mad-pro-cli 1.1.0 ā 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +32 -3
- package/lib/commands/add.js +72 -0
- package/lib/commands/create.js +644 -0
- package/lib/commands/doctor/architecture-checker.js +140 -0
- package/lib/commands/doctor/diff-reviewer.js +40 -0
- package/lib/commands/doctor/security-scanner.js +93 -0
- package/lib/commands/doctor.js +140 -0
- package/lib/commands/init.js +145 -52
- package/lib/commands/list.js +42 -0
- package/lib/commands/prompt.js +128 -0
- package/lib/utils/config.js +32 -0
- package/package.json +6 -2
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import initCommand from './init.js';
|
|
7
|
+
|
|
8
|
+
export default async function createCommand(projectName) {
|
|
9
|
+
console.log(chalk.bold.magenta('\nšļø MAD Pro - New Project Scaffold'));
|
|
10
|
+
console.log(chalk.gray('Generate a production-ready Android/KMP project.\n'));
|
|
11
|
+
|
|
12
|
+
if (!projectName) {
|
|
13
|
+
const res = await inquirer.prompt([{
|
|
14
|
+
type: 'input',
|
|
15
|
+
name: 'name',
|
|
16
|
+
message: 'Project name:',
|
|
17
|
+
default: 'MyMadApp',
|
|
18
|
+
validate: v => /^[A-Za-z][A-Za-z0-9]*$/.test(v) || 'Must start with a letter, alphanumeric only.'
|
|
19
|
+
}]);
|
|
20
|
+
projectName = res.name;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const answers = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'list',
|
|
26
|
+
name: 'templateType',
|
|
27
|
+
message: 'Select Template:',
|
|
28
|
+
choices: [
|
|
29
|
+
{ name: 'KrossWave KMP (Recommended - Multi-module, KMP, Koin, Voyager)', value: 'kmp' },
|
|
30
|
+
{ name: 'Basic Android (Single module, Hilt, Navigation Compose)', value: 'basic' }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'input',
|
|
35
|
+
name: 'package',
|
|
36
|
+
message: 'Package name:',
|
|
37
|
+
default: `com.example.${projectName.toLowerCase()}`,
|
|
38
|
+
validate: v => /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/.test(v) || 'Invalid package (e.g. com.example.app)'
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
let basicAnswers = {};
|
|
43
|
+
if (answers.templateType === 'basic') {
|
|
44
|
+
basicAnswers = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'list',
|
|
47
|
+
name: 'minSdk',
|
|
48
|
+
message: 'Minimum SDK:',
|
|
49
|
+
choices: ['24', '26', '28', '30'],
|
|
50
|
+
default: '26'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'list',
|
|
54
|
+
name: 'targetSdk',
|
|
55
|
+
message: 'Target SDK:',
|
|
56
|
+
choices: ['34', '35'],
|
|
57
|
+
default: '35'
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(targetDir)) {
|
|
65
|
+
console.error(chalk.red(`\nā Directory "${projectName}" already exists.`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pkg = answers.package;
|
|
70
|
+
const pkgPath = pkg.replace(/\./g, '/');
|
|
71
|
+
|
|
72
|
+
if (answers.templateType === 'kmp') {
|
|
73
|
+
console.log(chalk.cyan(`\nšØ Cloning KrossWave KMP template...\n`));
|
|
74
|
+
try {
|
|
75
|
+
execSync(`git clone --depth 1 https://github.com/derohimat/android-kmp-starter.git "${projectName}"`, { stdio: 'inherit' });
|
|
76
|
+
await fs.remove(path.join(targetDir, '.git'));
|
|
77
|
+
console.log(` ${chalk.green('ā')} Template cloned successfully`);
|
|
78
|
+
|
|
79
|
+
console.log(chalk.cyan(`\nšØ Customizing package name to ${pkg}...\n`));
|
|
80
|
+
const oldPkg = 'com.derohimat.krosswave';
|
|
81
|
+
const oldAppName = 'KrossWave'; // typical app name in that repo
|
|
82
|
+
|
|
83
|
+
async function processFolder(dir) {
|
|
84
|
+
const files = await fs.readdir(dir);
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
const fullPath = path.join(dir, file);
|
|
87
|
+
const stat = await fs.stat(fullPath);
|
|
88
|
+
|
|
89
|
+
if (stat.isDirectory()) {
|
|
90
|
+
await processFolder(fullPath);
|
|
91
|
+
} else {
|
|
92
|
+
const ext = path.extname(fullPath);
|
|
93
|
+
const validExts = ['.kt', '.xml', '.gradle', '.kts', '.pro', '.properties', '.toml', '.md'];
|
|
94
|
+
if (validExts.includes(ext) || file === 'gradlew' || file === 'gradlew.bat') {
|
|
95
|
+
try {
|
|
96
|
+
let content = await fs.readFile(fullPath, 'utf8');
|
|
97
|
+
let modified = false;
|
|
98
|
+
|
|
99
|
+
if (content.includes(oldPkg)) {
|
|
100
|
+
content = content.replace(new RegExp(oldPkg, 'g'), pkg);
|
|
101
|
+
modified = true;
|
|
102
|
+
}
|
|
103
|
+
if (content.includes(oldAppName)) {
|
|
104
|
+
content = content.replace(new RegExp(oldAppName, 'g'), projectName);
|
|
105
|
+
modified = true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (modified) {
|
|
109
|
+
await fs.writeFile(fullPath, content, 'utf8');
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// ignore read errors on binary files
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await processFolder(targetDir);
|
|
120
|
+
|
|
121
|
+
async function restructureDirs(currentDir) {
|
|
122
|
+
const items = await fs.readdir(currentDir);
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
const fullPath = path.join(currentDir, item);
|
|
125
|
+
if ((await fs.stat(fullPath)).isDirectory()) {
|
|
126
|
+
if (fullPath.endsWith(path.join('com', 'derohimat', 'krosswave'))) {
|
|
127
|
+
const newPath = fullPath.replace(path.join('com', 'derohimat', 'krosswave'), path.join(...pkg.split('.')));
|
|
128
|
+
await fs.move(fullPath, newPath, { overwrite: true });
|
|
129
|
+
|
|
130
|
+
const derohimatDir = path.dirname(fullPath);
|
|
131
|
+
const comDir = path.dirname(derohimatDir);
|
|
132
|
+
try {
|
|
133
|
+
await fs.rmdir(derohimatDir);
|
|
134
|
+
await fs.rmdir(comDir);
|
|
135
|
+
} catch(e) { }
|
|
136
|
+
} else {
|
|
137
|
+
await restructureDirs(fullPath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await restructureDirs(targetDir);
|
|
143
|
+
|
|
144
|
+
console.log(` ${chalk.green('ā')} Package renamed to ${pkg}`);
|
|
145
|
+
console.log(` ${chalk.green('ā')} App name updated to ${projectName}\n`);
|
|
146
|
+
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error(chalk.red(`\nā Failed to clone or process template: ${e.message}`));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// Generate Basic Android (existing logic)
|
|
153
|
+
console.log(chalk.cyan(`\nšØ Generating Basic Android project structure...\n`));
|
|
154
|
+
|
|
155
|
+
const compileSdk = basicAnswers.targetSdk;
|
|
156
|
+
const minSdk = basicAnswers.minSdk;
|
|
157
|
+
const targetSdk = basicAnswers.targetSdk;
|
|
158
|
+
|
|
159
|
+
const dirs = [
|
|
160
|
+
`app/src/main/java/${pkgPath}`,
|
|
161
|
+
`app/src/main/java/${pkgPath}/ui/theme`,
|
|
162
|
+
`app/src/main/java/${pkgPath}/ui/navigation`,
|
|
163
|
+
`app/src/main/java/${pkgPath}/ui/screens/home`,
|
|
164
|
+
`app/src/main/java/${pkgPath}/domain/model`,
|
|
165
|
+
`app/src/main/java/${pkgPath}/domain/usecase`,
|
|
166
|
+
`app/src/main/java/${pkgPath}/domain/repository`,
|
|
167
|
+
`app/src/main/java/${pkgPath}/data/repository`,
|
|
168
|
+
`app/src/main/java/${pkgPath}/data/remote`,
|
|
169
|
+
`app/src/main/java/${pkgPath}/data/local`,
|
|
170
|
+
`app/src/main/java/${pkgPath}/di`,
|
|
171
|
+
`app/src/main/res/drawable`,
|
|
172
|
+
`app/src/main/res/values`,
|
|
173
|
+
`app/src/main/res/mipmap-hdpi`,
|
|
174
|
+
`app/src/test/java/${pkgPath}`,
|
|
175
|
+
`app/src/androidTest/java/${pkgPath}`,
|
|
176
|
+
`gradle/wrapper`,
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
for (const d of dirs) {
|
|
180
|
+
await fs.ensureDir(path.join(targetDir, d));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const files = {};
|
|
184
|
+
files['build.gradle.kts'] = `// Top-level build file
|
|
185
|
+
plugins {
|
|
186
|
+
alias(libs.plugins.android.application) apply false
|
|
187
|
+
alias(libs.plugins.kotlin.android) apply false
|
|
188
|
+
alias(libs.plugins.kotlin.compose) apply false
|
|
189
|
+
alias(libs.plugins.hilt.android) apply false
|
|
190
|
+
alias(libs.plugins.ksp) apply false
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
files['settings.gradle.kts'] = `pluginManagement {
|
|
195
|
+
repositories {
|
|
196
|
+
google {
|
|
197
|
+
content {
|
|
198
|
+
includeGroupByRegex("com\\\\.android.*")
|
|
199
|
+
includeGroupByRegex("com\\\\.google.*")
|
|
200
|
+
includeGroupByRegex("androidx.*")
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
mavenCentral()
|
|
204
|
+
gradlePluginPortal()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
dependencyResolutionManagement {
|
|
208
|
+
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
|
209
|
+
repositories {
|
|
210
|
+
google()
|
|
211
|
+
mavenCentral()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
rootProject.name = "${projectName}"
|
|
216
|
+
include(":app")
|
|
217
|
+
`;
|
|
218
|
+
|
|
219
|
+
files['gradle/libs.versions.toml'] = `[versions]
|
|
220
|
+
agp = "8.7.3"
|
|
221
|
+
kotlin = "2.1.0"
|
|
222
|
+
ksp = "2.1.0-1.0.29"
|
|
223
|
+
coreKtx = "1.15.0"
|
|
224
|
+
lifecycle = "2.8.7"
|
|
225
|
+
activityCompose = "1.9.3"
|
|
226
|
+
composeBom = "2024.12.01"
|
|
227
|
+
hilt = "2.53.1"
|
|
228
|
+
hiltNavCompose = "1.2.0"
|
|
229
|
+
navigation = "2.8.5"
|
|
230
|
+
coroutines = "1.9.0"
|
|
231
|
+
junit = "4.13.2"
|
|
232
|
+
junitExt = "1.2.1"
|
|
233
|
+
espresso = "3.6.1"
|
|
234
|
+
|
|
235
|
+
[libraries]
|
|
236
|
+
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
|
237
|
+
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
|
238
|
+
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
|
239
|
+
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
|
240
|
+
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
|
241
|
+
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
|
242
|
+
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
243
|
+
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
|
244
|
+
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|
245
|
+
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
246
|
+
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
247
|
+
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
|
|
248
|
+
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
|
|
249
|
+
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
|
|
250
|
+
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavCompose" }
|
|
251
|
+
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
|
|
252
|
+
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
|
253
|
+
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitExt" }
|
|
254
|
+
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
|
|
255
|
+
|
|
256
|
+
[plugins]
|
|
257
|
+
android-application = { id = "com.android.application", version.ref = "agp" }
|
|
258
|
+
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
259
|
+
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
260
|
+
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
|
261
|
+
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
files['app/build.gradle.kts'] = `plugins {
|
|
265
|
+
alias(libs.plugins.android.application)
|
|
266
|
+
alias(libs.plugins.kotlin.android)
|
|
267
|
+
alias(libs.plugins.kotlin.compose)
|
|
268
|
+
alias(libs.plugins.hilt.android)
|
|
269
|
+
alias(libs.plugins.ksp)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
android {
|
|
273
|
+
namespace = "${pkg}"
|
|
274
|
+
compileSdk = ${compileSdk}
|
|
275
|
+
|
|
276
|
+
defaultConfig {
|
|
277
|
+
applicationId = "${pkg}"
|
|
278
|
+
minSdk = ${minSdk}
|
|
279
|
+
targetSdk = ${targetSdk}
|
|
280
|
+
versionCode = 1
|
|
281
|
+
versionName = "1.0"
|
|
282
|
+
|
|
283
|
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
buildTypes {
|
|
287
|
+
release {
|
|
288
|
+
isMinifyEnabled = true
|
|
289
|
+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
compileOptions {
|
|
294
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
295
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
kotlinOptions {
|
|
299
|
+
jvmTarget = "17"
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
buildFeatures {
|
|
303
|
+
compose = true
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
dependencies {
|
|
308
|
+
// Core
|
|
309
|
+
implementation(libs.androidx.core.ktx)
|
|
310
|
+
implementation(libs.androidx.lifecycle.runtime.ktx)
|
|
311
|
+
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
312
|
+
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
313
|
+
implementation(libs.androidx.activity.compose)
|
|
314
|
+
|
|
315
|
+
// Compose
|
|
316
|
+
implementation(platform(libs.androidx.compose.bom))
|
|
317
|
+
implementation(libs.androidx.ui)
|
|
318
|
+
implementation(libs.androidx.ui.graphics)
|
|
319
|
+
implementation(libs.androidx.ui.tooling.preview)
|
|
320
|
+
implementation(libs.androidx.material3)
|
|
321
|
+
|
|
322
|
+
// Navigation
|
|
323
|
+
implementation(libs.androidx.navigation.compose)
|
|
324
|
+
|
|
325
|
+
// Hilt
|
|
326
|
+
implementation(libs.hilt.android)
|
|
327
|
+
ksp(libs.hilt.compiler)
|
|
328
|
+
implementation(libs.hilt.navigation.compose)
|
|
329
|
+
|
|
330
|
+
// Coroutines
|
|
331
|
+
implementation(libs.kotlinx.coroutines.android)
|
|
332
|
+
|
|
333
|
+
// Testing
|
|
334
|
+
testImplementation(libs.junit)
|
|
335
|
+
androidTestImplementation(libs.androidx.junit)
|
|
336
|
+
androidTestImplementation(libs.androidx.espresso.core)
|
|
337
|
+
debugImplementation(libs.androidx.ui.tooling)
|
|
338
|
+
}
|
|
339
|
+
`;
|
|
340
|
+
|
|
341
|
+
files['gradle.properties'] = `# Project-wide Gradle settings.
|
|
342
|
+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
343
|
+
org.gradle.parallel=true
|
|
344
|
+
org.gradle.caching=true
|
|
345
|
+
android.useAndroidX=true
|
|
346
|
+
kotlin.code.style=official
|
|
347
|
+
android.nonTransitiveRClass=true
|
|
348
|
+
`;
|
|
349
|
+
|
|
350
|
+
files['gradle/wrapper/gradle-wrapper.properties'] = `distributionBase=GRADLE_USER_HOME
|
|
351
|
+
distributionPath=wrapper/dists
|
|
352
|
+
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
|
353
|
+
networkTimeout=10000
|
|
354
|
+
validateDistributionUrl=true
|
|
355
|
+
zipStoreBase=GRADLE_USER_HOME
|
|
356
|
+
zipStorePath=wrapper/dists
|
|
357
|
+
`;
|
|
358
|
+
|
|
359
|
+
files['app/src/main/AndroidManifest.xml'] = `<?xml version="1.0" encoding="utf-8"?>
|
|
360
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
361
|
+
|
|
362
|
+
<application
|
|
363
|
+
android:name=".${projectName}Application"
|
|
364
|
+
android:allowBackup=true
|
|
365
|
+
android:icon="@mipmap/ic_launcher"
|
|
366
|
+
android:label="${projectName}"
|
|
367
|
+
android:supportsRtl="true"
|
|
368
|
+
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
|
369
|
+
<activity
|
|
370
|
+
android:name=".MainActivity"
|
|
371
|
+
android:exported="true">
|
|
372
|
+
<intent-filter>
|
|
373
|
+
<action android:name="android.intent.action.MAIN" />
|
|
374
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
375
|
+
</intent-filter>
|
|
376
|
+
</activity>
|
|
377
|
+
</application>
|
|
378
|
+
|
|
379
|
+
</manifest>
|
|
380
|
+
`;
|
|
381
|
+
|
|
382
|
+
files[`app/src/main/java/${pkgPath}/${projectName}Application.kt`] = `package ${pkg}
|
|
383
|
+
|
|
384
|
+
import android.app.Application
|
|
385
|
+
import dagger.hilt.android.HiltAndroidApp
|
|
386
|
+
|
|
387
|
+
@HiltAndroidApp
|
|
388
|
+
class ${projectName}Application : Application()
|
|
389
|
+
`;
|
|
390
|
+
|
|
391
|
+
files[`app/src/main/java/${pkgPath}/MainActivity.kt`] = `package ${pkg}
|
|
392
|
+
|
|
393
|
+
import android.os.Bundle
|
|
394
|
+
import androidx.activity.ComponentActivity
|
|
395
|
+
import androidx.activity.compose.setContent
|
|
396
|
+
import androidx.activity.enableEdgeToEdge
|
|
397
|
+
import ${pkg}.ui.navigation.AppNavHost
|
|
398
|
+
import ${pkg}.ui.theme.${projectName}Theme
|
|
399
|
+
import dagger.hilt.android.AndroidEntryPoint
|
|
400
|
+
|
|
401
|
+
@AndroidEntryPoint
|
|
402
|
+
class MainActivity : ComponentActivity() {
|
|
403
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
404
|
+
super.onCreate(savedInstanceState)
|
|
405
|
+
enableEdgeToEdge()
|
|
406
|
+
setContent {
|
|
407
|
+
${projectName}Theme {
|
|
408
|
+
AppNavHost()
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
`;
|
|
414
|
+
|
|
415
|
+
files[`app/src/main/java/${pkgPath}/ui/theme/Color.kt`] = `package ${pkg}.ui.theme
|
|
416
|
+
|
|
417
|
+
import androidx.compose.ui.graphics.Color
|
|
418
|
+
|
|
419
|
+
val Purple80 = Color(0xFFD0BCFF)
|
|
420
|
+
val PurpleGrey80 = Color(0xFFCCC2DC)
|
|
421
|
+
val Pink80 = Color(0xFFEFB8C8)
|
|
422
|
+
|
|
423
|
+
val Purple40 = Color(0xFF6650a4)
|
|
424
|
+
val PurpleGrey40 = Color(0xFF625b71)
|
|
425
|
+
val Pink40 = Color(0xFF7D5260)
|
|
426
|
+
`;
|
|
427
|
+
|
|
428
|
+
files[`app/src/main/java/${pkgPath}/ui/theme/Type.kt`] = `package ${pkg}.ui.theme
|
|
429
|
+
|
|
430
|
+
import androidx.compose.material3.Typography
|
|
431
|
+
import androidx.compose.ui.text.TextStyle
|
|
432
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
433
|
+
import androidx.compose.ui.unit.sp
|
|
434
|
+
|
|
435
|
+
val Typography = Typography(
|
|
436
|
+
displayLarge = TextStyle(
|
|
437
|
+
fontWeight = FontWeight.Bold,
|
|
438
|
+
fontSize = 57.sp,
|
|
439
|
+
lineHeight = 64.sp,
|
|
440
|
+
letterSpacing = (-0.25).sp
|
|
441
|
+
),
|
|
442
|
+
headlineMedium = TextStyle(
|
|
443
|
+
fontWeight = FontWeight.SemiBold,
|
|
444
|
+
fontSize = 28.sp,
|
|
445
|
+
lineHeight = 36.sp
|
|
446
|
+
),
|
|
447
|
+
bodyLarge = TextStyle(
|
|
448
|
+
fontWeight = FontWeight.Normal,
|
|
449
|
+
fontSize = 16.sp,
|
|
450
|
+
lineHeight = 24.sp,
|
|
451
|
+
letterSpacing = 0.5.sp
|
|
452
|
+
),
|
|
453
|
+
labelSmall = TextStyle(
|
|
454
|
+
fontWeight = FontWeight.Medium,
|
|
455
|
+
fontSize = 11.sp,
|
|
456
|
+
lineHeight = 16.sp,
|
|
457
|
+
letterSpacing = 0.5.sp
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
`;
|
|
461
|
+
|
|
462
|
+
files[`app/src/main/java/${pkgPath}/ui/theme/Theme.kt`] = `package ${pkg}.ui.theme
|
|
463
|
+
|
|
464
|
+
import android.os.Build
|
|
465
|
+
import androidx.compose.foundation.isSystemInDarkTheme
|
|
466
|
+
import androidx.compose.material3.MaterialTheme
|
|
467
|
+
import androidx.compose.material3.darkColorScheme
|
|
468
|
+
import androidx.compose.material3.dynamicDarkColorScheme
|
|
469
|
+
import androidx.compose.material3.dynamicLightColorScheme
|
|
470
|
+
import androidx.compose.material3.lightColorScheme
|
|
471
|
+
import androidx.compose.runtime.Composable
|
|
472
|
+
import androidx.compose.ui.platform.LocalContext
|
|
473
|
+
|
|
474
|
+
private val DarkColorScheme = darkColorScheme(
|
|
475
|
+
primary = Purple80,
|
|
476
|
+
secondary = PurpleGrey80,
|
|
477
|
+
tertiary = Pink80
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
private val LightColorScheme = lightColorScheme(
|
|
481
|
+
primary = Purple40,
|
|
482
|
+
secondary = PurpleGrey40,
|
|
483
|
+
tertiary = Pink40
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
@Composable
|
|
487
|
+
fun ${projectName}Theme(
|
|
488
|
+
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
489
|
+
dynamicColor: Boolean = true,
|
|
490
|
+
content: @Composable () -> Unit
|
|
491
|
+
) {
|
|
492
|
+
val colorScheme = when {
|
|
493
|
+
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
|
494
|
+
val context = LocalContext.current
|
|
495
|
+
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
|
496
|
+
}
|
|
497
|
+
darkTheme -> DarkColorScheme
|
|
498
|
+
else -> LightColorScheme
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
MaterialTheme(
|
|
502
|
+
colorScheme = colorScheme,
|
|
503
|
+
typography = Typography,
|
|
504
|
+
content = content
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
`;
|
|
508
|
+
|
|
509
|
+
files[`app/src/main/java/${pkgPath}/ui/navigation/AppNavHost.kt`] = `package ${pkg}.ui.navigation
|
|
510
|
+
|
|
511
|
+
import androidx.compose.runtime.Composable
|
|
512
|
+
import androidx.navigation.compose.NavHost
|
|
513
|
+
import androidx.navigation.compose.composable
|
|
514
|
+
import androidx.navigation.compose.rememberNavController
|
|
515
|
+
import ${pkg}.ui.screens.home.HomeScreen
|
|
516
|
+
|
|
517
|
+
@Composable
|
|
518
|
+
fun AppNavHost() {
|
|
519
|
+
val navController = rememberNavController()
|
|
520
|
+
NavHost(navController = navController, startDestination = "home") {
|
|
521
|
+
composable("home") {
|
|
522
|
+
HomeScreen()
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
`;
|
|
527
|
+
|
|
528
|
+
files[`app/src/main/java/${pkgPath}/ui/screens/home/HomeScreen.kt`] = `package ${pkg}.ui.screens.home
|
|
529
|
+
|
|
530
|
+
import androidx.compose.foundation.layout.Box
|
|
531
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
532
|
+
import androidx.compose.foundation.layout.padding
|
|
533
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
534
|
+
import androidx.compose.material3.MaterialTheme
|
|
535
|
+
import androidx.compose.material3.Scaffold
|
|
536
|
+
import androidx.compose.material3.Text
|
|
537
|
+
import androidx.compose.material3.TopAppBar
|
|
538
|
+
import androidx.compose.runtime.Composable
|
|
539
|
+
import androidx.compose.ui.Alignment
|
|
540
|
+
import androidx.compose.ui.Modifier
|
|
541
|
+
import androidx.hilt.navigation.compose.hiltViewModel
|
|
542
|
+
|
|
543
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
544
|
+
@Composable
|
|
545
|
+
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
|
|
546
|
+
Scaffold(
|
|
547
|
+
topBar = {
|
|
548
|
+
TopAppBar(title = { Text("${projectName}") })
|
|
549
|
+
}
|
|
550
|
+
) { padding ->
|
|
551
|
+
Box(
|
|
552
|
+
modifier = Modifier
|
|
553
|
+
.fillMaxSize()
|
|
554
|
+
.padding(padding),
|
|
555
|
+
contentAlignment = Alignment.Center
|
|
556
|
+
) {
|
|
557
|
+
Text(
|
|
558
|
+
text = "Welcome to ${projectName}!",
|
|
559
|
+
style = MaterialTheme.typography.headlineMedium
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
`;
|
|
565
|
+
|
|
566
|
+
files[`app/src/main/java/${pkgPath}/ui/screens/home/HomeViewModel.kt`] = `package ${pkg}.ui.screens.home
|
|
567
|
+
|
|
568
|
+
import androidx.lifecycle.ViewModel
|
|
569
|
+
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
570
|
+
import javax.inject.Inject
|
|
571
|
+
|
|
572
|
+
@HiltViewModel
|
|
573
|
+
class HomeViewModel @Inject constructor() : ViewModel()
|
|
574
|
+
`;
|
|
575
|
+
|
|
576
|
+
files[`app/src/main/java/${pkgPath}/di/AppModule.kt`] = `package ${pkg}.di
|
|
577
|
+
|
|
578
|
+
import dagger.Module
|
|
579
|
+
import dagger.hilt.InstallIn
|
|
580
|
+
import dagger.hilt.components.SingletonComponent
|
|
581
|
+
|
|
582
|
+
@Module
|
|
583
|
+
@InstallIn(SingletonComponent::class)
|
|
584
|
+
object AppModule {
|
|
585
|
+
// Provide your app-wide dependencies here
|
|
586
|
+
}
|
|
587
|
+
`;
|
|
588
|
+
|
|
589
|
+
files['app/src/main/res/values/strings.xml'] = `<resources>
|
|
590
|
+
<string name="app_name">${projectName}</string>
|
|
591
|
+
</resources>
|
|
592
|
+
`;
|
|
593
|
+
|
|
594
|
+
files['app/src/main/res/values/colors.xml'] = `<?xml version="1.0" encoding="utf-8"?>
|
|
595
|
+
<resources>
|
|
596
|
+
<color name="black">#FF000000</color>
|
|
597
|
+
<color name="white">#FFFFFFFF</color>
|
|
598
|
+
</resources>
|
|
599
|
+
`;
|
|
600
|
+
|
|
601
|
+
files['app/proguard-rules.pro'] = `# Add project specific ProGuard rules here.
|
|
602
|
+
# -keep class ${pkg}.data.remote.dto.** { *; }
|
|
603
|
+
`;
|
|
604
|
+
|
|
605
|
+
files['.gitignore'] = `*.iml
|
|
606
|
+
.gradle
|
|
607
|
+
/local.properties
|
|
608
|
+
/.idea
|
|
609
|
+
.DS_Store
|
|
610
|
+
/build
|
|
611
|
+
/captures
|
|
612
|
+
.externalNativeBuild
|
|
613
|
+
.cxx
|
|
614
|
+
local.properties
|
|
615
|
+
`;
|
|
616
|
+
|
|
617
|
+
let fileCount = 0;
|
|
618
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
619
|
+
const fullPath = path.join(targetDir, filePath);
|
|
620
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
621
|
+
await fs.writeFile(fullPath, content);
|
|
622
|
+
fileCount++;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
console.log(` ${chalk.green('ā')} Generated ${fileCount} files.`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// āā Chain to Skill Wizard āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
629
|
+
console.log(chalk.yellow('\nā© Launching Skill Wizard...\n'));
|
|
630
|
+
|
|
631
|
+
const originalCwd = process.cwd();
|
|
632
|
+
process.chdir(targetDir);
|
|
633
|
+
await initCommand({});
|
|
634
|
+
process.chdir(originalCwd);
|
|
635
|
+
|
|
636
|
+
console.log(chalk.bold.green(`\nš Project "${projectName}" is ready!`));
|
|
637
|
+
console.log(chalk.white(`\n cd ${projectName}`));
|
|
638
|
+
|
|
639
|
+
if (answers.templateType === 'kmp') {
|
|
640
|
+
console.log(chalk.white(` ./gradlew composeApp:run\n`));
|
|
641
|
+
} else {
|
|
642
|
+
console.log(chalk.white(` ./gradlew assembleDebug\n`));
|
|
643
|
+
}
|
|
644
|
+
}
|