create-flutterinit 0.1.10 → 1.0.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.
Files changed (3) hide show
  1. package/bin/index.ts +3 -0
  2. package/dist/index.js +923 -923
  3. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -1237,11 +1237,11 @@ var require_ast = __commonJS((exports, module) => {
1237
1237
  helperExpression: function helperExpression(node) {
1238
1238
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
1239
1239
  },
1240
- scopedId: function scopedId(path2) {
1241
- return /^\.|this\b/.test(path2.original);
1240
+ scopedId: function scopedId(path) {
1241
+ return /^\.|this\b/.test(path.original);
1242
1242
  },
1243
- simpleId: function simpleId(path2) {
1244
- return path2.parts.length === 1 && !AST.helpers.scopedId(path2) && !path2.depth;
1243
+ simpleId: function simpleId(path) {
1244
+ return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
1245
1245
  }
1246
1246
  }
1247
1247
  };
@@ -2301,12 +2301,12 @@ var require_helpers2 = __commonJS((exports) => {
2301
2301
  loc
2302
2302
  };
2303
2303
  }
2304
- function prepareMustache(path2, params, hash, open, strip, locInfo) {
2304
+ function prepareMustache(path, params, hash, open, strip, locInfo) {
2305
2305
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
2306
2306
  var decorator = /\*/.test(open);
2307
2307
  return {
2308
2308
  type: decorator ? "Decorator" : "MustacheStatement",
2309
- path: path2,
2309
+ path,
2310
2310
  params,
2311
2311
  hash,
2312
2312
  escaped,
@@ -2618,9 +2618,9 @@ var require_compiler = __commonJS((exports) => {
2618
2618
  },
2619
2619
  DecoratorBlock: function DecoratorBlock(decorator) {
2620
2620
  var program = decorator.program && this.compileProgram(decorator.program);
2621
- var params = this.setupFullMustacheParams(decorator, program, undefined), path2 = decorator.path;
2621
+ var params = this.setupFullMustacheParams(decorator, program, undefined), path = decorator.path;
2622
2622
  this.useDecorators = true;
2623
- this.opcode("registerDecorator", params.length, path2.original);
2623
+ this.opcode("registerDecorator", params.length, path.original);
2624
2624
  },
2625
2625
  PartialStatement: function PartialStatement(partial) {
2626
2626
  this.usePartial = true;
@@ -2683,46 +2683,46 @@ var require_compiler = __commonJS((exports) => {
2683
2683
  }
2684
2684
  },
2685
2685
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
2686
- var path2 = sexpr.path, name = path2.parts[0], isBlock = program != null || inverse != null;
2687
- this.opcode("getContext", path2.depth);
2686
+ var path = sexpr.path, name = path.parts[0], isBlock = program != null || inverse != null;
2687
+ this.opcode("getContext", path.depth);
2688
2688
  this.opcode("pushProgram", program);
2689
2689
  this.opcode("pushProgram", inverse);
2690
- path2.strict = true;
2691
- this.accept(path2);
2690
+ path.strict = true;
2691
+ this.accept(path);
2692
2692
  this.opcode("invokeAmbiguous", name, isBlock);
2693
2693
  },
2694
2694
  simpleSexpr: function simpleSexpr(sexpr) {
2695
- var path2 = sexpr.path;
2696
- path2.strict = true;
2697
- this.accept(path2);
2695
+ var path = sexpr.path;
2696
+ path.strict = true;
2697
+ this.accept(path);
2698
2698
  this.opcode("resolvePossibleLambda");
2699
2699
  },
2700
2700
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
2701
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path2 = sexpr.path, name = path2.parts[0];
2701
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path = sexpr.path, name = path.parts[0];
2702
2702
  if (this.options.knownHelpers[name]) {
2703
2703
  this.opcode("invokeKnownHelper", params.length, name);
2704
2704
  } else if (this.options.knownHelpersOnly) {
2705
2705
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
2706
2706
  } else {
2707
- path2.strict = true;
2708
- path2.falsy = true;
2709
- this.accept(path2);
2710
- this.opcode("invokeHelper", params.length, path2.original, _ast2["default"].helpers.simpleId(path2));
2707
+ path.strict = true;
2708
+ path.falsy = true;
2709
+ this.accept(path);
2710
+ this.opcode("invokeHelper", params.length, path.original, _ast2["default"].helpers.simpleId(path));
2711
2711
  }
2712
2712
  },
2713
- PathExpression: function PathExpression(path2) {
2714
- this.addDepth(path2.depth);
2715
- this.opcode("getContext", path2.depth);
2716
- var name = path2.parts[0], scoped = _ast2["default"].helpers.scopedId(path2), blockParamId = !path2.depth && !scoped && this.blockParamIndex(name);
2713
+ PathExpression: function PathExpression(path) {
2714
+ this.addDepth(path.depth);
2715
+ this.opcode("getContext", path.depth);
2716
+ var name = path.parts[0], scoped = _ast2["default"].helpers.scopedId(path), blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
2717
2717
  if (blockParamId) {
2718
- this.opcode("lookupBlockParam", blockParamId, path2.parts);
2718
+ this.opcode("lookupBlockParam", blockParamId, path.parts);
2719
2719
  } else if (!name) {
2720
2720
  this.opcode("pushContext");
2721
- } else if (path2.data) {
2721
+ } else if (path.data) {
2722
2722
  this.options.data = true;
2723
- this.opcode("lookupData", path2.depth, path2.parts, path2.strict);
2723
+ this.opcode("lookupData", path.depth, path.parts, path.strict);
2724
2724
  } else {
2725
- this.opcode("lookupOnContext", path2.parts, path2.falsy, path2.strict, scoped);
2725
+ this.opcode("lookupOnContext", path.parts, path.falsy, path.strict, scoped);
2726
2726
  }
2727
2727
  },
2728
2728
  StringLiteral: function StringLiteral(string) {
@@ -3066,16 +3066,16 @@ var require_util = __commonJS((exports) => {
3066
3066
  }
3067
3067
  exports.urlGenerate = urlGenerate;
3068
3068
  function normalize(aPath) {
3069
- var path2 = aPath;
3069
+ var path = aPath;
3070
3070
  var url = urlParse(aPath);
3071
3071
  if (url) {
3072
3072
  if (!url.path) {
3073
3073
  return aPath;
3074
3074
  }
3075
- path2 = url.path;
3075
+ path = url.path;
3076
3076
  }
3077
- var isAbsolute = exports.isAbsolute(path2);
3078
- var parts = path2.split(/\/+/);
3077
+ var isAbsolute = exports.isAbsolute(path);
3078
+ var parts = path.split(/\/+/);
3079
3079
  for (var part, up = 0, i2 = parts.length - 1;i2 >= 0; i2--) {
3080
3080
  part = parts[i2];
3081
3081
  if (part === ".") {
@@ -3092,15 +3092,15 @@ var require_util = __commonJS((exports) => {
3092
3092
  }
3093
3093
  }
3094
3094
  }
3095
- path2 = parts.join("/");
3096
- if (path2 === "") {
3097
- path2 = isAbsolute ? "/" : ".";
3095
+ path = parts.join("/");
3096
+ if (path === "") {
3097
+ path = isAbsolute ? "/" : ".";
3098
3098
  }
3099
3099
  if (url) {
3100
- url.path = path2;
3100
+ url.path = path;
3101
3101
  return urlGenerate(url);
3102
3102
  }
3103
- return path2;
3103
+ return path;
3104
3104
  }
3105
3105
  exports.normalize = normalize;
3106
3106
  function join2(aRoot, aPath) {
@@ -5659,8 +5659,8 @@ var require_printer = __commonJS((exports) => {
5659
5659
  return this.accept(sexpr.path) + " " + params + hash;
5660
5660
  };
5661
5661
  PrintVisitor.prototype.PathExpression = function(id) {
5662
- var path2 = id.parts.join("/");
5663
- return (id.data ? "@" : "") + "PATH:" + path2;
5662
+ var path = id.parts.join("/");
5663
+ return (id.data ? "@" : "") + "PATH:" + path;
5664
5664
  };
5665
5665
  PrintVisitor.prototype.StringLiteral = function(string) {
5666
5666
  return '"' + string.value + '"';
@@ -7360,822 +7360,175 @@ ${r2}
7360
7360
  }
7361
7361
  }).prompt();
7362
7362
 
7363
- // src/utils/exec.ts
7364
- import { execSync } from "child_process";
7365
- function exec2(command, options) {
7366
- return execSync(command, {
7367
- stdio: "pipe",
7368
- cwd: options?.cwd,
7369
- encoding: "utf-8"
7370
- });
7363
+ // src/generator.ts
7364
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
7365
+ import fs3 from "fs";
7366
+ import path3 from "path";
7367
+
7368
+ // src/native.ts
7369
+ import { readFileSync, writeFileSync } from "fs";
7370
+ import { join } from "path";
7371
+ async function configureNativeFiles(config) {
7372
+ if (!hasAnyNativeFeature(config))
7373
+ return;
7374
+ await configureAndroid(config);
7375
+ await configureIos(config);
7371
7376
  }
7372
- function execVisible(command, options) {
7373
- execSync(command, {
7374
- stdio: "inherit",
7375
- cwd: options?.cwd
7376
- });
7377
+ function hasAnyNativeFeature(config) {
7378
+ return config.usesCamera || config.usesImagePicker || config.usesFilePicker || config.usesGeolocator || config.usesNotifications;
7377
7379
  }
7380
+ async function configureAndroid(config) {
7381
+ const manifestPath = join(config.outputDir, "android", "app", "src", "main", "AndroidManifest.xml");
7382
+ try {
7383
+ let manifest = readFileSync(manifestPath, "utf-8");
7384
+ const permissions = buildAndroidPermissions(config);
7385
+ if (permissions.length === 0)
7386
+ return;
7387
+ const unique = [...new Set(permissions)];
7388
+ const permissionsBlock = unique.map((p2) => ` <uses-permission android:name="${p2}"/>`).join(`
7389
+ `);
7390
+ manifest = manifest.replace(/(\s*)<application/, `
7391
+ ${permissionsBlock}
7378
7392
 
7379
- // src/utils/logger.ts
7380
- var import_picocolors = __toESM(require_picocolors(), 1);
7381
- import process2 from "node:process";
7382
- var brand = (str) => import_picocolors.default.bold(import_picocolors.default.blue(str));
7383
- var dim = (str) => import_picocolors.default.dim(str);
7384
- var accent = (str) => import_picocolors.default.cyan(str);
7385
- var warn = (str) => import_picocolors.default.yellow(str);
7386
- var error = (str) => import_picocolors.default.red(str);
7387
- function isUnicodeSupported2() {
7388
- if (process2.platform !== "win32") {
7389
- return process2.env.TERM !== "linux";
7393
+ $1<application`);
7394
+ writeFileSync(manifestPath, manifest, "utf-8");
7395
+ } catch (err) {
7396
+ log.warn(`AndroidManifest.xml configuration failed: ${err.message}`);
7397
+ log.warn(`File path: ${manifestPath}`);
7398
+ logManualAndroidInstructions(config);
7399
+ return;
7390
7400
  }
7391
- return Boolean(process2.env.WT_SESSION) || Boolean(process2.env.TERMINUS_SUBLIME) || process2.env.ConEmuTask === "{cmd::Cmder}" || process2.env.TERM_PROGRAM === "Terminus-Sublime" || process2.env.TERM_PROGRAM === "vscode" || process2.env.TERM === "xterm-256color" || process2.env.TERM === "alacritty" || process2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
7392
- }
7393
- var BANNER_UNICODE = `
7394
- ${brand(" ███████╗██╗ ██╗ ██╗████████╗████████╗███████╗██████╗ ██╗███╗ ██╗██╗████████╗")}
7395
- ${brand(" ██╔════╝██║ ██║ ██║╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗ ██║████╗ ██║██║╚══██╔══╝")}
7396
- ${brand(" █████╗ ██║ ██║ ██║ ██║ ██║ █████╗ ██████╔╝ ██║██╔██╗ ██║██║ ██║ ")}
7397
- ${brand(" ██╔══╝ ██║ ██║ ██║ ██║ ██║ ██╔══╝ ██╔══██╗ ██║██║╚██╗██║██║ ██║ ")}
7398
- ${brand(" ██║ ███████╗╚██████╔╝ ██║ ██║ ███████╗██║ ██║ ██║██║ ╚████║██║ ██║ ")}
7399
- ${brand(" ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ")}
7400
-
7401
- ${dim("Scaffold production-ready Flutter projects from your terminal")}
7402
- ${dim("─────────────────────────────────────────────────────────────")}
7403
- ${dim("v0.1.0")} ${import_picocolors.default.bold("·")} ${accent("flutterinit.com")} ${import_picocolors.default.bold("·")} ${dim("by Arjun Mahar")}
7404
- `;
7405
- var BANNER_ASCII = `
7406
- ${brand(" ####### ## ## ## ######## ######## ####### ###### ## ### ## ## ########")}
7407
- ${brand(" ## ## ## ## ## ## ## ## ## ## #### ## ## ## ")}
7408
- ${brand(" ##### ## ## ## ## ## ##### ###### ## ## ## ## ## ## ")}
7409
- ${brand(" ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ")}
7410
- ${brand(" ## ####### ###### ## ## ####### ## ## ## ## #### ## ## ")}
7411
-
7412
- ${dim("Scaffold production-ready Flutter projects from your terminal")}
7413
- ${dim("-------------------------------------------------------------")}
7414
- ${dim("v0.1.0")} ${import_picocolors.default.bold("-")} ${accent("flutterinit.com")} ${import_picocolors.default.bold("-")} ${dim("by Arjun Mahar")}
7415
- `;
7416
- function printBanner() {
7417
- console.log(isUnicodeSupported2() ? BANNER_UNICODE : BANNER_ASCII);
7401
+ await configureAndroidBuildGradle(config);
7418
7402
  }
7419
- function printStep(title, detail) {
7420
- const bullet = isUnicodeSupported2() ? "" : ">";
7421
- console.log();
7422
- console.log(` ${brand(bullet)} ${import_picocolors.default.bold(title)}`);
7423
- console.log(` ${dim(detail)}`);
7424
- console.log();
7403
+ async function configureAndroidBuildGradle(config) {
7404
+ const gradlePath = join(config.outputDir, "android", "app", "build.gradle");
7405
+ try {} catch (err) {
7406
+ log.warn(`build.gradle configuration failed: ${err.message}`);
7407
+ }
7425
7408
  }
7426
- function logWarn(message) {
7427
- log.warn(warn(message));
7409
+ function buildAndroidPermissions(config) {
7410
+ const permissions = [];
7411
+ if (config.usesCamera || config.usesImagePicker) {
7412
+ permissions.push("android.permission.CAMERA", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE");
7413
+ }
7414
+ if (config.usesFilePicker) {
7415
+ permissions.push("android.permission.READ_EXTERNAL_STORAGE");
7416
+ }
7417
+ if (config.usesGeolocator) {
7418
+ permissions.push("android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION");
7419
+ }
7420
+ if (config.usesNotifications) {
7421
+ permissions.push("android.permission.RECEIVE_BOOT_COMPLETED", "android.permission.VIBRATE", "android.permission.WAKE_LOCK", "android.permission.POST_NOTIFICATIONS");
7422
+ }
7423
+ return permissions;
7428
7424
  }
7429
- function logError(message) {
7430
- log.error(error(message));
7425
+ async function configureIos(config) {
7426
+ const plistPath = join(config.outputDir, "ios", "Runner", "Info.plist");
7427
+ try {
7428
+ let plist = readFileSync(plistPath, "utf-8");
7429
+ const entries = buildIosPlistEntries(config);
7430
+ if (entries.length === 0)
7431
+ return;
7432
+ const entriesBlock = entries.join(`
7433
+ `);
7434
+ plist = plist.replace(/(<\/dict>\s*<\/plist>)/, `${entriesBlock}
7435
+ $1`);
7436
+ writeFileSync(plistPath, plist, "utf-8");
7437
+ } catch (err) {
7438
+ log.warn(`Info.plist configuration failed: ${err.message}`);
7439
+ log.warn(`File path: ${plistPath}`);
7440
+ logManualIosInstructions(config);
7441
+ return;
7442
+ }
7443
+ await configureIosPodfile(config);
7431
7444
  }
7432
- function logInfo(message) {
7433
- log.info(dim(message));
7445
+ async function configureIosPodfile(config) {
7446
+ const podfilePath = join(config.outputDir, "ios", "Podfile");
7447
+ try {} catch (err) {
7448
+ log.warn(`Podfile configuration failed: ${err.message}`);
7449
+ }
7434
7450
  }
7435
-
7436
- // src/preflight.ts
7437
- var import_picocolors2 = __toESM(require_picocolors(), 1);
7438
- async function runPreflight() {
7439
- const flutterSpinner = spinner();
7440
- flutterSpinner.start("Checking Flutter installation...");
7441
- try {
7442
- const version = exec2("flutter --version");
7443
- const versionLine = version.split(`
7444
- `)[0]?.trim() ?? "Flutter (version unknown)";
7445
- flutterSpinner.stop(`${import_picocolors2.default.green("✓")} ${versionLine}`);
7446
- } catch {
7447
- flutterSpinner.stop(import_picocolors2.default.red("✗ Flutter not found on PATH"));
7448
- logError("Flutter SDK is required but was not found.");
7449
- log.message(` Install Flutter: ${import_picocolors2.default.cyan("https://flutter.dev/docs/get-started/install")}`);
7450
- process.exit(1);
7451
+ function buildIosPlistEntries(config) {
7452
+ const entries = [];
7453
+ if (config.usesCamera || config.usesImagePicker) {
7454
+ entries.push("\t<key>NSCameraUsageDescription</key>", "\t<string>This app requires camera access</string>", "\t<key>NSPhotoLibraryUsageDescription</key>", "\t<string>This app requires photo library access</string>", "\t<key>NSPhotoLibraryAddUsageDescription</key>", "\t<string>This app requires photo library write access</string>");
7451
7455
  }
7452
- try {
7453
- const bunVersion = exec2("bun --version").trim();
7454
- const [major] = bunVersion.split(".").map(Number);
7455
- if (major < 1) {
7456
- logWarn(`Bun v${bunVersion} detected. Bun 1.0.0+ is recommended.`);
7457
- } else {
7458
- logInfo(`Bun v${bunVersion}`);
7456
+ if (config.usesFilePicker) {
7457
+ entries.push("\t<key>NSPhotoLibraryUsageDescription</key>", "\t<string>This app requires photo library access</string>");
7458
+ }
7459
+ if (config.usesGeolocator) {
7460
+ entries.push("\t<key>NSLocationWhenInUseUsageDescription</key>", "\t<string>This app requires location access</string>", "\t<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>", "\t<string>This app requires location access</string>");
7461
+ }
7462
+ if (config.usesNotifications) {}
7463
+ return deduplicatePlistEntries(entries);
7464
+ }
7465
+ function deduplicatePlistEntries(entries) {
7466
+ const seen = new Set;
7467
+ const result = [];
7468
+ for (let i2 = 0;i2 < entries.length; i2++) {
7469
+ const line = entries[i2];
7470
+ if (line.includes("<key>")) {
7471
+ if (seen.has(line)) {
7472
+ i2++;
7473
+ continue;
7474
+ }
7475
+ seen.add(line);
7459
7476
  }
7460
- } catch {
7461
- logWarn("Could not detect Bun version. Continuing anyway.");
7477
+ result.push(line);
7462
7478
  }
7463
- console.log();
7464
- log.message(`${brand("─────────────────────────────────────────────────────────────")}
7465
- ` + ` Running ${import_picocolors2.default.bold("flutter doctor")} to check your environment...
7466
- ` + `${brand("─────────────────────────────────────────────────────────────")}`);
7467
- console.log();
7468
- try {
7469
- execVisible("flutter doctor");
7470
- } catch {}
7471
- console.log();
7472
- const shouldContinue = await confirm({
7473
- message: "Flutter doctor ran above. Do you want to continue?",
7474
- initialValue: true
7475
- });
7476
- if (isCancel(shouldContinue) || !shouldContinue) {
7477
- cancel("Fix any Flutter issues first, then run create-flutterinit again.");
7478
- log.message(` ${import_picocolors2.default.cyan("https://flutter.dev/docs/get-started/install")}`);
7479
- process.exit(0);
7479
+ return result;
7480
+ }
7481
+ function logManualAndroidInstructions(config) {
7482
+ const permissions = buildAndroidPermissions(config);
7483
+ if (permissions.length === 0)
7484
+ return;
7485
+ log.warn("Add these manually to android/app/src/main/AndroidManifest.xml");
7486
+ log.warn(`Place them before the <application tag:
7487
+ `);
7488
+ for (const p2 of [...new Set(permissions)]) {
7489
+ log.message(` <uses-permission android:name="${p2}"/>`);
7490
+ }
7491
+ }
7492
+ function logManualIosInstructions(config) {
7493
+ const entries = buildIosPlistEntries(config);
7494
+ if (entries.length === 0)
7495
+ return;
7496
+ log.warn("Add these manually to ios/Runner/Info.plist");
7497
+ log.warn(`Place them before the closing </dict> tag:
7498
+ `);
7499
+ for (const entry of entries) {
7500
+ log.message(entry);
7480
7501
  }
7481
7502
  }
7482
7503
 
7483
- // src/prompts.ts
7484
- var import_picocolors3 = __toESM(require_picocolors(), 1);
7504
+ // src/templates.ts
7505
+ var import_handlebars = __toESM(require_lib(), 1);
7506
+ import fs from "fs";
7485
7507
  import path from "path";
7486
-
7487
- // src/config.ts
7488
- var ARCHITECTURE_LABELS = {
7489
- clean: "Clean Architecture",
7490
- mvvm: "MVVM",
7491
- "feature-first": "Feature-First",
7492
- mvc: "MVC",
7493
- "layer-first": "Layer-First"
7494
- };
7495
- var STATE_LABELS = {
7496
- riverpod: "Riverpod",
7497
- bloc: "Bloc / Cubit",
7498
- provider: "Provider",
7499
- mobx: "MobX",
7500
- getx: "GetX"
7501
- };
7502
- var BACKEND_LABELS = {
7503
- firebase: "Firebase",
7504
- supabase: "Supabase",
7505
- appwrite: "Appwrite",
7506
- custom: "Custom Backend",
7507
- none: "None"
7508
- };
7509
- var NAVIGATION_LABELS = {
7510
- gorouter: "GoRouter",
7511
- autoroute: "AutoRoute",
7512
- none: "Navigator 2.0"
7513
- };
7514
- var THEME_LABELS = {
7515
- light: "Light only",
7516
- dark: "Dark only",
7517
- both: "Both (system)"
7518
- };
7519
-
7520
- // src/prompts.ts
7521
- function checkCancel(value) {
7522
- if (isCancel(value)) {
7523
- cancel("Cancelled. Run create-flutterinit again whenever you're ready.");
7524
- process.exit(0);
7525
- }
7526
- }
7527
- async function runPrompts() {
7528
- printStep("Project Identity", "Basic information about your Flutter application.");
7529
- const projectName = await text({
7530
- message: `Project name ${import_picocolors3.default.dim("(lowercase letters, numbers, and underscores)")}`,
7531
- placeholder: "my_app",
7532
- validate(value) {
7533
- if (!value || value.trim().length === 0)
7534
- return "Project name is required.";
7535
- if (!/^[a-z][a-z0-9_]*$/.test(value.trim()))
7536
- return "Must be lowercase with underscores only — e.g. my_app";
7537
- return;
7538
- }
7539
- });
7540
- checkCancel(projectName);
7541
- const name = projectName.trim();
7542
- const orgName = await text({
7543
- message: `Organisation name ${import_picocolors3.default.dim("(reverse domain format — e.g. com.yourcompany)")}`,
7544
- placeholder: "com.example",
7545
- defaultValue: "com.example",
7546
- validate(value) {
7547
- const v = value || "com.example";
7548
- if (!/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/.test(v))
7549
- return "Use reverse domain format — e.g. com.example or com.acme.mobile";
7550
- return;
7551
- }
7552
- });
7553
- checkCancel(orgName);
7554
- const description = await text({
7555
- message: "Project description",
7556
- placeholder: "A new Flutter project",
7557
- defaultValue: "A new Flutter project"
7558
- });
7559
- checkCancel(description);
7560
- printStep("Architecture", "Choose how your project folders and layers are organized.");
7561
- const architecture = await select({
7562
- message: "Architecture pattern",
7563
- options: [
7564
- {
7565
- value: "clean",
7566
- label: "Clean Architecture",
7567
- hint: "Data → Domain → Presentation layers. Best for large teams."
7568
- },
7569
- {
7570
- value: "mvvm",
7571
- label: "MVVM",
7572
- hint: "Model-View-ViewModel. Great with Riverpod or Provider."
7573
- },
7574
- {
7575
- value: "feature-first",
7576
- label: "Feature-First",
7577
- hint: "Organized by feature, not layer. Scales well for medium apps."
7578
- },
7579
- {
7580
- value: "mvc",
7581
- label: "MVC",
7582
- hint: "Model-View-Controller. Familiar pattern for most developers."
7583
- },
7584
- {
7585
- value: "layer-first",
7586
- label: "Layer-First",
7587
- hint: "Global shared layers. Simple, good for smaller apps."
7588
- }
7589
- ]
7590
- });
7591
- checkCancel(architecture);
7592
- printStep("State Management", "The state management solution wired throughout your app.");
7593
- const stateManager = await select({
7594
- message: "State manager",
7595
- options: [
7596
- {
7597
- value: "riverpod",
7598
- label: "Riverpod",
7599
- hint: "AsyncNotifier + Riverpod Generator. Compile-safe & testable."
7600
- },
7601
- {
7602
- value: "bloc",
7603
- label: "Bloc / Cubit",
7604
- hint: "Event-driven. Strict separation, excellent for large teams."
7605
- },
7606
- {
7607
- value: "provider",
7608
- label: "Provider",
7609
- hint: "Simple InheritedWidget wrapper. Great for smaller apps."
7610
- },
7611
- {
7612
- value: "mobx",
7613
- label: "MobX",
7614
- hint: "Reactive observables with code generation."
7615
- },
7616
- {
7617
- value: "getx",
7618
- label: "GetX",
7619
- hint: "All-in-one: state, routing, DI. Opinionated but fast."
7620
- }
7621
- ]
7622
- });
7623
- checkCancel(stateManager);
7624
- printStep("Backend", "Backend-as-a-service to wire into your data layer.");
7625
- const backend = await select({
7626
- message: "Backend service",
7627
- options: [
7628
- {
7629
- value: "firebase",
7630
- label: "Firebase",
7631
- hint: "Auth, Firestore, Storage, FCM. Google ecosystem."
7632
- },
7633
- {
7634
- value: "supabase",
7635
- label: "Supabase",
7636
- hint: "Open-source Firebase alternative. Postgres + realtime."
7637
- },
7638
- {
7639
- value: "appwrite",
7640
- label: "Appwrite",
7641
- hint: "Self-hostable BaaS. Full ownership of your data."
7642
- },
7643
- {
7644
- value: "custom",
7645
- label: "Custom Backend",
7646
- hint: "Connect to your own REST API/service via AppConfig."
7647
- },
7648
- {
7649
- value: "none",
7650
- label: "None",
7651
- hint: "No backend wired in. Add your own later."
7652
- }
7653
- ]
7654
- });
7655
- checkCancel(backend);
7656
- printStep("Navigation", "Routing solution for navigating between screens.");
7657
- const navigation = await select({
7658
- message: "Navigation package",
7659
- options: [
7660
- {
7661
- value: "gorouter",
7662
- label: "GoRouter",
7663
- hint: "Official Flutter routing. URL-based, deep-link ready."
7664
- },
7665
- {
7666
- value: "autoroute",
7667
- label: "AutoRoute",
7668
- hint: "Code-generated typed routes. Zero string-based navigation."
7669
- },
7670
- {
7671
- value: "none",
7672
- label: "Navigator 2.0",
7673
- hint: "Vanilla Flutter navigation. No extra dependency."
7674
- }
7675
- ]
7676
- });
7677
- checkCancel(navigation);
7678
- printStep("Theme & Appearance", "Material 3 color scheme and theme mode for your app.");
7679
- const themeMode = await select({
7680
- message: "Theme mode",
7681
- options: [
7682
- {
7683
- value: "both",
7684
- label: "Both (system default)",
7685
- hint: "App respects the device light/dark preference."
7686
- },
7687
- {
7688
- value: "light",
7689
- label: "Light only",
7690
- hint: "Always renders in light mode."
7691
- },
7692
- {
7693
- value: "dark",
7694
- label: "Dark only",
7695
- hint: "Always renders in dark mode."
7696
- }
7697
- ]
7698
- });
7699
- checkCancel(themeMode);
7700
- const primaryColor = await text({
7701
- message: `Primary color ${import_picocolors3.default.dim("(hex seed color for color scheme)")}`,
7702
- placeholder: "#027DFD",
7703
- defaultValue: "#027DFD",
7704
- validate(value) {
7705
- const v = value || "#027DFD";
7706
- if (!/^#[0-9A-Fa-f]{6}$/.test(v))
7707
- return "Enter a valid 6-digit hex color — e.g. #6750A4";
7708
- return;
7709
- }
7710
- });
7711
- checkCancel(primaryColor);
7712
- printStep("Optional Utilities & Features", "Select additional packages and features to pre-configure in your codebase.");
7713
- const selectedMiscResult = await groupMultiselect({
7714
- message: "Select packages to include (Press Space to select & Enter to confirm)",
7715
- options: {
7716
- "Icon Packs": [
7717
- {
7718
- value: "usesIconsaxPlus",
7719
- label: "Iconsax Plus",
7720
- hint: "Modern icon set with 6 distinct styles (linear, bold, etc.)"
7721
- },
7722
- {
7723
- value: "usesFlutterRemix",
7724
- label: "Flutter Remix",
7725
- hint: "Remix Icon library package wrapper"
7726
- },
7727
- {
7728
- value: "usesHugeicons",
7729
- label: "Hugeicons",
7730
- hint: "Free stroke outline icons pack"
7731
- }
7732
- ],
7733
- "Networking & Storage": [
7734
- {
7735
- value: "usesDio",
7736
- label: "Dio",
7737
- hint: "Powerful HTTP client with interceptors, form data & caching [Recommended]"
7738
- },
7739
- {
7740
- value: "usesHttp",
7741
- label: "HTTP Client",
7742
- hint: "Official lightweight Dart http package"
7743
- },
7744
- {
7745
- value: "usesCachedNetworkImage",
7746
- label: "Cached Network Image",
7747
- hint: "Download, render and cache network images automatically [Popular]"
7748
- },
7749
- {
7750
- value: "usesHive",
7751
- label: "Hive Database",
7752
- hint: "Lightweight & blazing fast key-value NoSQL database"
7753
- },
7754
- {
7755
- value: "usesSharedPreferences",
7756
- label: "Shared Preferences",
7757
- hint: "Platform-persistent key-value pairs storage [Essential]"
7758
- },
7759
- {
7760
- value: "usesSecureStorage",
7761
- label: "Secure Storage",
7762
- hint: "Store credentials/sensitive data securely (Keychain/Keystore)"
7763
- }
7764
- ],
7765
- "Media & Assets": [
7766
- {
7767
- value: "usesFlutterSvg",
7768
- label: "Flutter SVG",
7769
- hint: "Vector SVG rendering support [Popular]"
7770
- },
7771
- {
7772
- value: "usesImagePicker",
7773
- label: "Image Picker",
7774
- hint: "Pick images/videos from gallery or shoot new ones with camera"
7775
- },
7776
- {
7777
- value: "usesCamera",
7778
- label: "Camera",
7779
- hint: "camera package — full camera control + recording"
7780
- },
7781
- {
7782
- value: "usesFilePicker",
7783
- label: "File Picker",
7784
- hint: "Native file explorer to upload files/documents"
7785
- },
7786
- {
7787
- value: "usesFlutterNativeSplash",
7788
- label: "Flutter Native Splash",
7789
- hint: "Automatic native splash screens config"
7790
- }
7791
- ],
7792
- "Essential Utilities": [
7793
- {
7794
- value: "usesUrlLauncher",
7795
- label: "URL Launcher",
7796
- hint: "Trigger browser URLs, map locations, SMS, and telephone calls [Essential]"
7797
- },
7798
- {
7799
- value: "usesPathProvider",
7800
- label: "Path Provider",
7801
- hint: "Locate commonly used app folders on device filesystem [Essential]"
7802
- },
7803
- {
7804
- value: "usesSharePlus",
7805
- label: "Share Plus",
7806
- hint: "Trigger native system share panels for links, images & text"
7807
- },
7808
- {
7809
- value: "usesPermissionHandler",
7810
- label: "Permission Handler",
7811
- hint: "Query and request system hardware permissions dynamically [Essential]"
7812
- },
7813
- {
7814
- value: "usesDeviceInfoPlus",
7815
- label: "Device Info",
7816
- hint: "Access deep hardware model/OS version properties"
7817
- },
7818
- {
7819
- value: "usesGeolocator",
7820
- label: "Geolocator",
7821
- hint: "Acquire and track device GPS location updates"
7822
- },
7823
- {
7824
- value: "usesNotifications",
7825
- label: "Local Notifications",
7826
- hint: "flutter_local_notifications — schedule and display local alerts"
7827
- },
7828
- {
7829
- value: "usesAppVersionUpdate",
7830
- label: "App Version Update",
7831
- hint: "Verify and alert users when an app update is available"
7832
- }
7833
- ],
7834
- "Advanced Features": [
7835
- {
7836
- value: "usesFlutterHooks",
7837
- label: "Flutter Hooks",
7838
- hint: "React-style code structure for widget lifecycle states [Popular]"
7839
- },
7840
- {
7841
- value: "usesSkeletonizer",
7842
- label: "Skeletonizer",
7843
- hint: "Transform simple widgets into custom shimmering loader states [UI]"
7844
- },
7845
- {
7846
- value: "usesScreenutil",
7847
- label: "ScreenUtil",
7848
- hint: "Sizing & font scaling adapter for responsive layouts [Popular]"
7849
- },
7850
- {
7851
- value: "usesDotenv",
7852
- label: "Environment Config (.env)",
7853
- hint: "Pre-configure flutter_dotenv for configuration files support"
7854
- },
7855
- {
7856
- value: "usesLogger",
7857
- label: "Console Logger",
7858
- hint: "Logger package for clean output filters"
7859
- },
7860
- {
7861
- value: "useLocalization",
7862
- label: "Localization (intl)",
7863
- hint: "Integrate native multi-language supported structures"
7864
- },
7865
- {
7866
- value: "useMaterial3",
7867
- label: "Material 3 support",
7868
- hint: "Configure global ThemeData to support M3 guidelines"
7869
- }
7870
- ]
7871
- },
7872
- required: false,
7873
- initialValues: [
7874
- "usesIconsaxPlus",
7875
- "usesDio",
7876
- "usesSharedPreferences",
7877
- "usesSecureStorage",
7878
- "usesCachedNetworkImage",
7879
- "usesFlutterSvg",
7880
- "usesFlutterNativeSplash",
7881
- "usesUrlLauncher",
7882
- "usesPathProvider",
7883
- "usesPermissionHandler",
7884
- "usesDeviceInfoPlus",
7885
- "usesAppVersionUpdate",
7886
- "usesScreenutil",
7887
- "usesDotenv",
7888
- "usesLogger",
7889
- "useLocalization",
7890
- "useMaterial3"
7891
- ]
7892
- });
7893
- checkCancel(selectedMiscResult);
7894
- const selectedMisc = selectedMiscResult;
7895
- const usesCamera = selectedMisc.includes("usesCamera");
7896
- const usesImagePicker = selectedMisc.includes("usesImagePicker");
7897
- const usesFilePicker = selectedMisc.includes("usesFilePicker");
7898
- const usesGeolocator = selectedMisc.includes("usesGeolocator");
7899
- const usesNotifications = selectedMisc.includes("usesNotifications");
7900
- const needsPermissionHandler = usesCamera || usesImagePicker || usesFilePicker || usesGeolocator || usesNotifications;
7901
- const usesPermissionHandler = selectedMisc.includes("usesPermissionHandler") || needsPermissionHandler;
7902
- const allSelected = [
7903
- ...selectedMisc.includes("usesIconsaxPlus") ? ["Iconsax Plus"] : [],
7904
- ...selectedMisc.includes("usesFlutterRemix") ? ["Flutter Remix"] : [],
7905
- ...selectedMisc.includes("usesHugeicons") ? ["Hugeicons"] : [],
7906
- ...selectedMisc.includes("usesDio") ? ["Dio"] : [],
7907
- ...selectedMisc.includes("usesHttp") ? ["HTTP"] : [],
7908
- ...selectedMisc.includes("usesCachedNetworkImage") ? ["Cached Image"] : [],
7909
- ...selectedMisc.includes("usesHive") ? ["Hive"] : [],
7910
- ...selectedMisc.includes("usesSharedPreferences") ? ["SharedPreferences"] : [],
7911
- ...selectedMisc.includes("usesSecureStorage") ? ["SecureStorage"] : [],
7912
- ...selectedMisc.includes("usesFlutterSvg") ? ["Flutter SVG"] : [],
7913
- ...usesImagePicker ? ["Image Picker"] : [],
7914
- ...usesCamera ? ["Camera"] : [],
7915
- ...usesFilePicker ? ["File Picker"] : [],
7916
- ...selectedMisc.includes("usesFlutterNativeSplash") ? ["Native Splash"] : [],
7917
- ...selectedMisc.includes("usesUrlLauncher") ? ["URL Launcher"] : [],
7918
- ...selectedMisc.includes("usesPathProvider") ? ["Path Provider"] : [],
7919
- ...selectedMisc.includes("usesSharePlus") ? ["Share Plus"] : [],
7920
- ...usesPermissionHandler ? ["Permission Handler"] : [],
7921
- ...selectedMisc.includes("usesDeviceInfoPlus") ? ["Device Info"] : [],
7922
- ...usesGeolocator ? ["Geolocator"] : [],
7923
- ...usesNotifications ? ["Local Notifications"] : [],
7924
- ...selectedMisc.includes("usesAppVersionUpdate") ? ["App Version Update"] : [],
7925
- ...selectedMisc.includes("usesFlutterHooks") ? ["Flutter Hooks"] : [],
7926
- ...selectedMisc.includes("usesSkeletonizer") ? ["Skeletonizer"] : [],
7927
- ...selectedMisc.includes("usesScreenutil") ? ["ScreenUtil"] : [],
7928
- ...selectedMisc.includes("usesDotenv") ? ["Dotenv"] : [],
7929
- ...selectedMisc.includes("usesLogger") ? ["Logger"] : [],
7930
- ...selectedMisc.includes("useLocalization") ? ["Localization"] : [],
7931
- ...selectedMisc.includes("useMaterial3") ? ["Material 3"] : []
7932
- ];
7933
- const outputDir = path.resolve(process.cwd(), name);
7934
- const nativeFeatures = [
7935
- usesCamera && "Camera",
7936
- usesImagePicker && "Image Picker",
7937
- usesFilePicker && "File Picker",
7938
- usesGeolocator && "Location",
7939
- usesNotifications && "Notifications"
7940
- ].filter(Boolean).join(", ");
7941
- note([
7942
- `${import_picocolors3.default.bold("Project")} ${import_picocolors3.default.cyan(name)}`,
7943
- `${import_picocolors3.default.bold("Org")} ${orgName || "com.example"}`,
7944
- `${import_picocolors3.default.bold("Description")} ${description || "A new Flutter project"}`,
7945
- ``,
7946
- `${import_picocolors3.default.bold("Architecture")} ${ARCHITECTURE_LABELS[architecture]}`,
7947
- `${import_picocolors3.default.bold("State")} ${STATE_LABELS[stateManager]}`,
7948
- `${import_picocolors3.default.bold("Backend")} ${BACKEND_LABELS[backend]}`,
7949
- `${import_picocolors3.default.bold("Navigation")} ${NAVIGATION_LABELS[navigation]}`,
7950
- ``,
7951
- `${import_picocolors3.default.bold("Theme")} ${THEME_LABELS[themeMode]}`,
7952
- `${import_picocolors3.default.bold("Color")} ${primaryColor || "#027DFD"}`,
7953
- `${import_picocolors3.default.bold("Utilities")} ${allSelected.length > 0 ? allSelected.join(", ") : "none"}`,
7954
- `${import_picocolors3.default.bold("Native")} ${nativeFeatures || "None"}`,
7955
- ``,
7956
- `${import_picocolors3.default.bold("Output")} ${import_picocolors3.default.dim(outputDir)}`
7957
- ].join(`
7958
- `), "Your FlutterInit Configuration");
7959
- const confirmed = await confirm({
7960
- message: "Generate this project?",
7961
- initialValue: true
7962
- });
7963
- if (isCancel(confirmed) || !confirmed) {
7964
- cancel("Generation cancelled. Run create-flutterinit again to start over.");
7965
- process.exit(0);
7966
- }
7967
- return {
7968
- projectName: name,
7969
- orgName: orgName || "com.example",
7970
- description: description || "A new Flutter project",
7971
- architecture,
7972
- stateManager,
7973
- backend,
7974
- navigation,
7975
- themeMode,
7976
- primaryColor: primaryColor || "#027DFD",
7977
- outputDir,
7978
- usesIconsaxPlus: selectedMisc.includes("usesIconsaxPlus"),
7979
- usesFlutterRemix: selectedMisc.includes("usesFlutterRemix"),
7980
- usesHugeicons: selectedMisc.includes("usesHugeicons"),
7981
- usesDio: selectedMisc.includes("usesDio"),
7982
- usesHttp: selectedMisc.includes("usesHttp"),
7983
- usesCachedNetworkImage: selectedMisc.includes("usesCachedNetworkImage"),
7984
- usesHive: selectedMisc.includes("usesHive"),
7985
- usesSharedPreferences: selectedMisc.includes("usesSharedPreferences"),
7986
- usesSecureStorage: selectedMisc.includes("usesSecureStorage"),
7987
- usesFlutterSvg: selectedMisc.includes("usesFlutterSvg"),
7988
- usesImagePicker,
7989
- usesCamera,
7990
- usesFilePicker,
7991
- usesFlutterNativeSplash: selectedMisc.includes("usesFlutterNativeSplash"),
7992
- usesUrlLauncher: selectedMisc.includes("usesUrlLauncher"),
7993
- usesPathProvider: selectedMisc.includes("usesPathProvider"),
7994
- usesSharePlus: selectedMisc.includes("usesSharePlus"),
7995
- usesPermissionHandler,
7996
- usesDeviceInfoPlus: selectedMisc.includes("usesDeviceInfoPlus"),
7997
- usesGeolocator,
7998
- usesNotifications,
7999
- usesAppVersionUpdate: selectedMisc.includes("usesAppVersionUpdate"),
8000
- usesFlutterHooks: selectedMisc.includes("usesFlutterHooks"),
8001
- usesSkeletonizer: selectedMisc.includes("usesSkeletonizer"),
8002
- usesScreenutil: selectedMisc.includes("usesScreenutil"),
8003
- usesDotenv: selectedMisc.includes("usesDotenv"),
8004
- usesLogger: selectedMisc.includes("usesLogger"),
8005
- useLocalization: selectedMisc.includes("useLocalization"),
8006
- useMaterial3: selectedMisc.includes("useMaterial3")
8007
- };
8008
- }
8009
-
8010
- // src/generator.ts
8011
- var import_picocolors4 = __toESM(require_picocolors(), 1);
8012
- import fs3 from "fs";
8013
- import path4 from "path";
8014
-
8015
- // src/native.ts
8016
- import { readFileSync, writeFileSync } from "fs";
8017
- import { join } from "path";
8018
- async function configureNativeFiles(config) {
8019
- if (!hasAnyNativeFeature(config))
8020
- return;
8021
- await configureAndroid(config);
8022
- await configureIos(config);
8023
- }
8024
- function hasAnyNativeFeature(config) {
8025
- return config.usesCamera || config.usesImagePicker || config.usesFilePicker || config.usesGeolocator || config.usesNotifications;
8026
- }
8027
- async function configureAndroid(config) {
8028
- const manifestPath = join(config.outputDir, "android", "app", "src", "main", "AndroidManifest.xml");
8029
- try {
8030
- let manifest = readFileSync(manifestPath, "utf-8");
8031
- const permissions = buildAndroidPermissions(config);
8032
- if (permissions.length === 0)
8033
- return;
8034
- const unique = [...new Set(permissions)];
8035
- const permissionsBlock = unique.map((p2) => ` <uses-permission android:name="${p2}"/>`).join(`
8036
- `);
8037
- manifest = manifest.replace(/(\s*)<application/, `
8038
- ${permissionsBlock}
8039
-
8040
- $1<application`);
8041
- writeFileSync(manifestPath, manifest, "utf-8");
8042
- } catch (err) {
8043
- log.warn(`AndroidManifest.xml configuration failed: ${err.message}`);
8044
- log.warn(`File path: ${manifestPath}`);
8045
- logManualAndroidInstructions(config);
8046
- return;
8047
- }
8048
- await configureAndroidBuildGradle(config);
8049
- }
8050
- async function configureAndroidBuildGradle(config) {
8051
- const gradlePath = join(config.outputDir, "android", "app", "build.gradle");
8052
- try {} catch (err) {
8053
- log.warn(`build.gradle configuration failed: ${err.message}`);
8054
- }
8055
- }
8056
- function buildAndroidPermissions(config) {
8057
- const permissions = [];
8058
- if (config.usesCamera || config.usesImagePicker) {
8059
- permissions.push("android.permission.CAMERA", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE");
8060
- }
8061
- if (config.usesFilePicker) {
8062
- permissions.push("android.permission.READ_EXTERNAL_STORAGE");
8063
- }
8064
- if (config.usesGeolocator) {
8065
- permissions.push("android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION");
8066
- }
8067
- if (config.usesNotifications) {
8068
- permissions.push("android.permission.RECEIVE_BOOT_COMPLETED", "android.permission.VIBRATE", "android.permission.WAKE_LOCK", "android.permission.POST_NOTIFICATIONS");
8069
- }
8070
- return permissions;
8071
- }
8072
- async function configureIos(config) {
8073
- const plistPath = join(config.outputDir, "ios", "Runner", "Info.plist");
8074
- try {
8075
- let plist = readFileSync(plistPath, "utf-8");
8076
- const entries = buildIosPlistEntries(config);
8077
- if (entries.length === 0)
8078
- return;
8079
- const entriesBlock = entries.join(`
8080
- `);
8081
- plist = plist.replace(/(<\/dict>\s*<\/plist>)/, `${entriesBlock}
8082
- $1`);
8083
- writeFileSync(plistPath, plist, "utf-8");
8084
- } catch (err) {
8085
- log.warn(`Info.plist configuration failed: ${err.message}`);
8086
- log.warn(`File path: ${plistPath}`);
8087
- logManualIosInstructions(config);
8088
- return;
8089
- }
8090
- await configureIosPodfile(config);
8091
- }
8092
- async function configureIosPodfile(config) {
8093
- const podfilePath = join(config.outputDir, "ios", "Podfile");
8094
- try {} catch (err) {
8095
- log.warn(`Podfile configuration failed: ${err.message}`);
8096
- }
8097
- }
8098
- function buildIosPlistEntries(config) {
8099
- const entries = [];
8100
- if (config.usesCamera || config.usesImagePicker) {
8101
- entries.push("\t<key>NSCameraUsageDescription</key>", "\t<string>This app requires camera access</string>", "\t<key>NSPhotoLibraryUsageDescription</key>", "\t<string>This app requires photo library access</string>", "\t<key>NSPhotoLibraryAddUsageDescription</key>", "\t<string>This app requires photo library write access</string>");
8102
- }
8103
- if (config.usesFilePicker) {
8104
- entries.push("\t<key>NSPhotoLibraryUsageDescription</key>", "\t<string>This app requires photo library access</string>");
8105
- }
8106
- if (config.usesGeolocator) {
8107
- entries.push("\t<key>NSLocationWhenInUseUsageDescription</key>", "\t<string>This app requires location access</string>", "\t<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>", "\t<string>This app requires location access</string>");
8108
- }
8109
- if (config.usesNotifications) {}
8110
- return deduplicatePlistEntries(entries);
8111
- }
8112
- function deduplicatePlistEntries(entries) {
8113
- const seen = new Set;
8114
- const result = [];
8115
- for (let i2 = 0;i2 < entries.length; i2++) {
8116
- const line = entries[i2];
8117
- if (line.includes("<key>")) {
8118
- if (seen.has(line)) {
8119
- i2++;
8120
- continue;
8121
- }
8122
- seen.add(line);
8123
- }
8124
- result.push(line);
8125
- }
8126
- return result;
8127
- }
8128
- function logManualAndroidInstructions(config) {
8129
- const permissions = buildAndroidPermissions(config);
8130
- if (permissions.length === 0)
8131
- return;
8132
- log.warn("Add these manually to android/app/src/main/AndroidManifest.xml");
8133
- log.warn(`Place them before the <application tag:
8134
- `);
8135
- for (const p2 of [...new Set(permissions)]) {
8136
- log.message(` <uses-permission android:name="${p2}"/>`);
8137
- }
8138
- }
8139
- function logManualIosInstructions(config) {
8140
- const entries = buildIosPlistEntries(config);
8141
- if (entries.length === 0)
8142
- return;
8143
- log.warn("Add these manually to ios/Runner/Info.plist");
8144
- log.warn(`Place them before the closing </dict> tag:
8145
- `);
8146
- for (const entry of entries) {
8147
- log.message(entry);
8148
- }
8149
- }
8150
-
8151
- // src/templates.ts
8152
- var import_handlebars = __toESM(require_lib(), 1);
8153
- import fs from "fs";
8154
- import path2 from "path";
8155
7508
  import { fileURLToPath } from "url";
8156
7509
  var __filename2 = fileURLToPath(import.meta.url);
8157
- var __dirname2 = path2.dirname(__filename2);
7510
+ var __dirname2 = path.dirname(__filename2);
8158
7511
  var TEMPLATE_ROOT = (() => {
8159
- const fromSrc = path2.resolve(__dirname2, "../../templates/flutter");
7512
+ const fromSrc = path.resolve(__dirname2, "../../templates/flutter");
8160
7513
  if (fs.existsSync(fromSrc))
8161
7514
  return fromSrc;
8162
- const fromDist = path2.resolve(__dirname2, "../templates");
7515
+ const fromDist = path.resolve(__dirname2, "../templates");
8163
7516
  if (fs.existsSync(fromDist))
8164
7517
  return fromDist;
8165
- return path2.resolve(process.cwd(), "templates");
7518
+ return path.resolve(process.cwd(), "templates");
8166
7519
  })();
8167
- var PARTIALS_ROOT = path2.join(TEMPLATE_ROOT, "partials");
7520
+ var PARTIALS_ROOT = path.join(TEMPLATE_ROOT, "partials");
8168
7521
  function registerPartialsSync(dir) {
8169
7522
  if (!fs.existsSync(dir))
8170
7523
  return;
8171
7524
  const entries = fs.readdirSync(dir, { withFileTypes: true });
8172
7525
  for (const entry of entries) {
8173
- const fullPath = path2.join(dir, entry.name);
7526
+ const fullPath = path.join(dir, entry.name);
8174
7527
  if (entry.isDirectory()) {
8175
7528
  registerPartialsSync(fullPath);
8176
7529
  } else if (entry.isFile() && entry.name.endsWith(".hbs")) {
8177
7530
  const contents = fs.readFileSync(fullPath, "utf8");
8178
- const rel = path2.relative(PARTIALS_ROOT, fullPath);
7531
+ const rel = path.relative(PARTIALS_ROOT, fullPath);
8179
7532
  const name = rel.replace(/\\/g, "/").replace(/\.hbs$/, "");
8180
7533
  import_handlebars.default.registerPartial(name, contents);
8181
7534
  }
@@ -8325,7 +7678,7 @@ function buildTemplateContext(config) {
8325
7678
  };
8326
7679
  }
8327
7680
  function renderTemplate(templatePath, config) {
8328
- const fullPath = path2.join(TEMPLATE_ROOT, templatePath);
7681
+ const fullPath = path.join(TEMPLATE_ROOT, templatePath);
8329
7682
  if (!fs.existsSync(fullPath)) {
8330
7683
  throw new Error(`Template not found: ${fullPath}`);
8331
7684
  }
@@ -8335,32 +7688,6 @@ function renderTemplate(templatePath, config) {
8335
7688
  return template(context);
8336
7689
  }
8337
7690
 
8338
- // src/utils/fs.ts
8339
- import fs2 from "fs";
8340
- import path3 from "path";
8341
- function writeFile(filePath, content) {
8342
- fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
8343
- fs2.writeFileSync(filePath, content, "utf-8");
8344
- }
8345
- function createGitkeep(dirPath) {
8346
- fs2.mkdirSync(dirPath, { recursive: true });
8347
- const keepFile = path3.join(dirPath, ".gitkeep");
8348
- if (!fs2.existsSync(keepFile)) {
8349
- fs2.writeFileSync(keepFile, "", "utf-8");
8350
- }
8351
- }
8352
- function removeDir(dirPath) {
8353
- try {
8354
- fs2.rmSync(dirPath, { recursive: true, force: true });
8355
- } catch {}
8356
- }
8357
- function isDirNonEmpty(dirPath) {
8358
- if (!fs2.existsSync(dirPath))
8359
- return false;
8360
- const entries = fs2.readdirSync(dirPath);
8361
- return entries.length > 0;
8362
- }
8363
-
8364
7691
  // src/utils/analytics.ts
8365
7692
  import crypto from "crypto";
8366
7693
  async function trackCliGeneration(config) {
@@ -8420,7 +7747,106 @@ async function trackCliGeneration(config) {
8420
7747
  clearTimeout(timeoutId);
8421
7748
  }
8422
7749
  }
8423
- } catch (error2) {}
7750
+ } catch (error) {}
7751
+ }
7752
+
7753
+ // src/utils/exec.ts
7754
+ import { execSync } from "child_process";
7755
+ function exec2(command, options) {
7756
+ return execSync(command, {
7757
+ stdio: "pipe",
7758
+ cwd: options?.cwd,
7759
+ encoding: "utf-8"
7760
+ });
7761
+ }
7762
+ function execVisible(command, options) {
7763
+ execSync(command, {
7764
+ stdio: "inherit",
7765
+ cwd: options?.cwd
7766
+ });
7767
+ }
7768
+
7769
+ // src/utils/fs.ts
7770
+ import fs2 from "fs";
7771
+ import path2 from "path";
7772
+ function writeFile(filePath, content) {
7773
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
7774
+ fs2.writeFileSync(filePath, content, "utf-8");
7775
+ }
7776
+ function createGitkeep(dirPath) {
7777
+ fs2.mkdirSync(dirPath, { recursive: true });
7778
+ const keepFile = path2.join(dirPath, ".gitkeep");
7779
+ if (!fs2.existsSync(keepFile)) {
7780
+ fs2.writeFileSync(keepFile, "", "utf-8");
7781
+ }
7782
+ }
7783
+ function removeDir(dirPath) {
7784
+ try {
7785
+ fs2.rmSync(dirPath, { recursive: true, force: true });
7786
+ } catch {}
7787
+ }
7788
+ function isDirNonEmpty(dirPath) {
7789
+ if (!fs2.existsSync(dirPath))
7790
+ return false;
7791
+ const entries = fs2.readdirSync(dirPath);
7792
+ return entries.length > 0;
7793
+ }
7794
+
7795
+ // src/utils/logger.ts
7796
+ var import_picocolors = __toESM(require_picocolors(), 1);
7797
+ import process2 from "node:process";
7798
+ var brand = (str) => import_picocolors.default.bold(import_picocolors.default.blue(str));
7799
+ var dim = (str) => import_picocolors.default.dim(str);
7800
+ var accent = (str) => import_picocolors.default.cyan(str);
7801
+ var warn = (str) => import_picocolors.default.yellow(str);
7802
+ var error = (str) => import_picocolors.default.red(str);
7803
+ function isUnicodeSupported2() {
7804
+ if (process2.platform !== "win32") {
7805
+ return process2.env.TERM !== "linux";
7806
+ }
7807
+ return Boolean(process2.env.WT_SESSION) || Boolean(process2.env.TERMINUS_SUBLIME) || process2.env.ConEmuTask === "{cmd::Cmder}" || process2.env.TERM_PROGRAM === "Terminus-Sublime" || process2.env.TERM_PROGRAM === "vscode" || process2.env.TERM === "xterm-256color" || process2.env.TERM === "alacritty" || process2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
7808
+ }
7809
+ var BANNER_UNICODE = `
7810
+ ${brand(" ███████╗██╗ ██╗ ██╗████████╗████████╗███████╗██████╗ ██╗███╗ ██╗██╗████████╗")}
7811
+ ${brand(" ██╔════╝██║ ██║ ██║╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗ ██║████╗ ██║██║╚══██╔══╝")}
7812
+ ${brand(" █████╗ ██║ ██║ ██║ ██║ ██║ █████╗ ██████╔╝ ██║██╔██╗ ██║██║ ██║ ")}
7813
+ ${brand(" ██╔══╝ ██║ ██║ ██║ ██║ ██║ ██╔══╝ ██╔══██╗ ██║██║╚██╗██║██║ ██║ ")}
7814
+ ${brand(" ██║ ███████╗╚██████╔╝ ██║ ██║ ███████╗██║ ██║ ██║██║ ╚████║██║ ██║ ")}
7815
+ ${brand(" ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ")}
7816
+
7817
+ ${dim("Scaffold production-ready Flutter projects from your terminal")}
7818
+ ${dim("─────────────────────────────────────────────────────────────")}
7819
+ ${dim("v0.1.0")} ${import_picocolors.default.bold("·")} ${accent("flutterinit.com")} ${import_picocolors.default.bold("·")} ${dim("by Arjun Mahar")}
7820
+ `;
7821
+ var BANNER_ASCII = `
7822
+ ${brand(" ####### ## ## ## ######## ######## ####### ###### ## ### ## ## ########")}
7823
+ ${brand(" ## ## ## ## ## ## ## ## ## ## #### ## ## ## ")}
7824
+ ${brand(" ##### ## ## ## ## ## ##### ###### ## ## ## ## ## ## ")}
7825
+ ${brand(" ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ")}
7826
+ ${brand(" ## ####### ###### ## ## ####### ## ## ## ## #### ## ## ")}
7827
+
7828
+ ${dim("Scaffold production-ready Flutter projects from your terminal")}
7829
+ ${dim("-------------------------------------------------------------")}
7830
+ ${dim("v0.1.0")} ${import_picocolors.default.bold("-")} ${accent("flutterinit.com")} ${import_picocolors.default.bold("-")} ${dim("by Arjun Mahar")}
7831
+ `;
7832
+ function printBanner() {
7833
+ console.log(isUnicodeSupported2() ? BANNER_UNICODE : BANNER_ASCII);
7834
+ }
7835
+ function printStep(title, detail) {
7836
+ const bullet = isUnicodeSupported2() ? "◆" : ">";
7837
+ console.log();
7838
+ console.log(` ${brand(bullet)} ${import_picocolors.default.bold(title)}`);
7839
+ console.log(` ${dim(detail)}`);
7840
+ console.log();
7841
+ }
7842
+ function logWarn(message) {
7843
+ log.warn(warn(message));
7844
+ }
7845
+ function logError(message) {
7846
+ log.error(error(message));
7847
+ }
7848
+ function logInfo(message) {
7849
+ log.info(dim(message));
8424
7850
  }
8425
7851
 
8426
7852
  // src/generator.ts
@@ -8519,25 +7945,25 @@ function overlayTemplateDir(templateDir, outputDir, config) {
8519
7945
  return;
8520
7946
  const entries = fs3.readdirSync(templateDir, { withFileTypes: true });
8521
7947
  for (const entry of entries) {
8522
- const srcPath = path4.join(templateDir, entry.name);
7948
+ const srcPath = path3.join(templateDir, entry.name);
8523
7949
  if (entry.isDirectory()) {
8524
- const destDir = path4.join(outputDir, entry.name);
7950
+ const destDir = path3.join(outputDir, entry.name);
8525
7951
  overlayTemplateDir(srcPath, destDir, config);
8526
7952
  } else if (entry.name.endsWith(".hbs")) {
8527
7953
  const nameWithoutHbs = entry.name.slice(0, -4);
8528
7954
  const outputName = resolveConditionalFilename(nameWithoutHbs, config);
8529
7955
  if (outputName === null)
8530
7956
  continue;
8531
- const outputPath = path4.join(outputDir, outputName);
8532
- const relPath = path4.relative(TEMPLATE_ROOT, srcPath);
7957
+ const outputPath = path3.join(outputDir, outputName);
7958
+ const relPath = path3.relative(TEMPLATE_ROOT, srcPath);
8533
7959
  const rendered = renderTemplate(relPath, config);
8534
7960
  writeFile(outputPath, rendered);
8535
7961
  } else {
8536
7962
  const outputName = resolveConditionalFilename(entry.name, config);
8537
7963
  if (outputName === null)
8538
7964
  continue;
8539
- const outputPath = path4.join(outputDir, outputName);
8540
- fs3.mkdirSync(path4.dirname(outputPath), { recursive: true });
7965
+ const outputPath = path3.join(outputDir, outputName);
7966
+ fs3.mkdirSync(path3.dirname(outputPath), { recursive: true });
8541
7967
  fs3.copyFileSync(srcPath, outputPath);
8542
7968
  }
8543
7969
  }
@@ -8545,7 +7971,7 @@ function overlayTemplateDir(templateDir, outputDir, config) {
8545
7971
  async function generateProject(config) {
8546
7972
  const { outputDir, projectName, orgName, architecture, backend, navigation } = config;
8547
7973
  if (isDirNonEmpty(outputDir)) {
8548
- logWarn(`Directory already exists and is not empty: ${import_picocolors4.default.dim(outputDir)}`);
7974
+ logWarn(`Directory already exists and is not empty: ${import_picocolors2.default.dim(outputDir)}`);
8549
7975
  const overwrite = await confirm({
8550
7976
  message: "Overwrite the existing directory?",
8551
7977
  initialValue: false
@@ -8560,93 +7986,667 @@ async function generateProject(config) {
8560
7986
  s.start("Running flutter create...");
8561
7987
  try {
8562
7988
  exec2(`flutter create --org ${orgName} --project-name ${projectName} "${outputDir}"`);
8563
- s.stop(`${import_picocolors4.default.green("✓")} Flutter project scaffolded`);
7989
+ s.stop(`${import_picocolors2.default.green("✓")} Flutter project scaffolded`);
8564
7990
  } catch (err) {
8565
- s.stop(import_picocolors4.default.red("✗ flutter create failed"));
7991
+ s.stop(import_picocolors2.default.red("✗ flutter create failed"));
8566
7992
  logError(`flutter create failed: ${err.message}`);
8567
7993
  removeDir(outputDir);
8568
7994
  process.exit(1);
8569
7995
  }
8570
7996
  s.start("Applying FlutterInit base templates...");
8571
7997
  try {
8572
- const baseTemplateDir = path4.join(TEMPLATE_ROOT, "base");
7998
+ const baseTemplateDir = path3.join(TEMPLATE_ROOT, "base");
8573
7999
  overlayTemplateDir(baseTemplateDir, outputDir, config);
8574
- s.stop(`${import_picocolors4.default.green("✓")} Base templates applied`);
8000
+ s.stop(`${import_picocolors2.default.green("✓")} Base templates applied`);
8575
8001
  } catch (err) {
8576
- s.stop(import_picocolors4.default.red("✗ Template overlay failed"));
8002
+ s.stop(import_picocolors2.default.red("✗ Template overlay failed"));
8577
8003
  logError(`Template rendering failed: ${err.message}`);
8578
8004
  removeDir(outputDir);
8579
8005
  process.exit(1);
8580
8006
  }
8581
- s.start(`Creating ${architecture} folder structure...`);
8582
- try {
8583
- const folders = ARCH_FOLDERS[architecture] ?? [];
8584
- for (const folder of folders) {
8585
- createGitkeep(path4.join(outputDir, folder));
8007
+ s.start(`Creating ${architecture} folder structure...`);
8008
+ try {
8009
+ const folders = ARCH_FOLDERS[architecture] ?? [];
8010
+ for (const folder of folders) {
8011
+ createGitkeep(path3.join(outputDir, folder));
8012
+ }
8013
+ s.stop(`${import_picocolors2.default.green("✓")} ${ARCH_FOLDERS[architecture]?.length ?? 0} directories created`);
8014
+ } catch (err) {
8015
+ s.stop(import_picocolors2.default.red("✗ Folder creation failed"));
8016
+ logError(`Failed to create architecture folders: ${err.message}`);
8017
+ removeDir(outputDir);
8018
+ process.exit(1);
8019
+ }
8020
+ s.start("Applying architecture templates...");
8021
+ try {
8022
+ const archTemplateDir = path3.join(TEMPLATE_ROOT, "overlays", "architecture", architecture);
8023
+ overlayTemplateDir(archTemplateDir, outputDir, config);
8024
+ s.stop(`${import_picocolors2.default.green("✓")} Architecture templates applied`);
8025
+ } catch (err) {
8026
+ s.stop(import_picocolors2.default.red("✗ Architecture template overlay failed"));
8027
+ logError(`Architecture templates failed: ${err.message}`);
8028
+ removeDir(outputDir);
8029
+ process.exit(1);
8030
+ }
8031
+ if (backend !== "none") {
8032
+ s.start(`Applying ${backend} backend templates...`);
8033
+ try {
8034
+ const backendTemplateDir = path3.join(TEMPLATE_ROOT, "overlays", "backend", backend);
8035
+ overlayTemplateDir(backendTemplateDir, outputDir, config);
8036
+ s.stop(`${import_picocolors2.default.green("✓")} ${backend} templates applied`);
8037
+ } catch (err) {
8038
+ s.stop(import_picocolors2.default.yellow(`⚠ Backend templates had issues — ${err.message}`));
8039
+ }
8040
+ }
8041
+ if (navigation !== "none") {
8042
+ const navName = navigation === "gorouter" ? "go_router" : "auto_route";
8043
+ s.start(`Applying ${navName} navigation templates...`);
8044
+ try {
8045
+ const navTemplateDir = path3.join(TEMPLATE_ROOT, "overlays", "navigation", navName);
8046
+ if (fs3.existsSync(navTemplateDir)) {
8047
+ overlayTemplateDir(navTemplateDir, outputDir, config);
8048
+ }
8049
+ s.stop(`${import_picocolors2.default.green("✓")} Navigation templates applied`);
8050
+ } catch (err) {
8051
+ s.stop(import_picocolors2.default.yellow(`⚠ Navigation templates had issues — ${err.message}`));
8052
+ }
8053
+ }
8054
+ const ns = spinner();
8055
+ ns.start("Configuring native permissions...");
8056
+ try {
8057
+ await configureNativeFiles(config);
8058
+ ns.stop(`${import_picocolors2.default.green("✓")} Native permissions configured`);
8059
+ } catch (err) {
8060
+ ns.stop(import_picocolors2.default.yellow("⚠ Native configuration skipped — check AndroidManifest.xml and Info.plist manually"));
8061
+ logWarn(String(err));
8062
+ }
8063
+ await trackCliGeneration(config);
8064
+ note([
8065
+ `${import_picocolors2.default.bold("cd")} "${outputDir}"`,
8066
+ `${import_picocolors2.default.bold("flutter pub get")}`,
8067
+ `${import_picocolors2.default.bold("flutter run")}`,
8068
+ ``,
8069
+ `${import_picocolors2.default.dim("Or open in VS Code:")}`,
8070
+ `${import_picocolors2.default.bold("code")} "${outputDir}"`,
8071
+ ``,
8072
+ `${import_picocolors2.default.dim("Docs & templates:")} ${import_picocolors2.default.cyan("https://flutterinit.com")}`
8073
+ ].join(`
8074
+ `), "Next steps");
8075
+ outro(`${brand(" FlutterInit ")} ${import_picocolors2.default.dim("—")} ${import_picocolors2.default.green("Your Flutter project is ready. Happy coding! \uD83D\uDE80")}`);
8076
+ }
8077
+
8078
+ // src/preflight.ts
8079
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
8080
+ async function runPreflight() {
8081
+ const flutterSpinner = spinner();
8082
+ flutterSpinner.start("Checking Flutter installation...");
8083
+ try {
8084
+ const version = exec2("flutter --version");
8085
+ const versionLine = version.split(`
8086
+ `)[0]?.trim() ?? "Flutter (version unknown)";
8087
+ flutterSpinner.stop(`${import_picocolors3.default.green("✓")} ${versionLine}`);
8088
+ } catch {
8089
+ flutterSpinner.stop(import_picocolors3.default.red("✗ Flutter not found on PATH"));
8090
+ logError("Flutter SDK is required but was not found.");
8091
+ log.message(` Install Flutter: ${import_picocolors3.default.cyan("https://flutter.dev/docs/get-started/install")}`);
8092
+ process.exit(1);
8093
+ }
8094
+ try {
8095
+ const bunVersion = exec2("bun --version").trim();
8096
+ const [major] = bunVersion.split(".").map(Number);
8097
+ if (major < 1) {
8098
+ logWarn(`Bun v${bunVersion} detected. Bun 1.0.0+ is recommended.`);
8099
+ } else {
8100
+ logInfo(`Bun v${bunVersion}`);
8101
+ }
8102
+ } catch {
8103
+ logWarn("Could not detect Bun version. Continuing anyway.");
8104
+ }
8105
+ console.log();
8106
+ log.message(`${brand("─────────────────────────────────────────────────────────────")}
8107
+ ` + ` Running ${import_picocolors3.default.bold("flutter doctor")} to check your environment...
8108
+ ` + `${brand("─────────────────────────────────────────────────────────────")}`);
8109
+ console.log();
8110
+ try {
8111
+ execVisible("flutter doctor");
8112
+ } catch {}
8113
+ console.log();
8114
+ const shouldContinue = await confirm({
8115
+ message: "Flutter doctor ran above. Do you want to continue?",
8116
+ initialValue: true
8117
+ });
8118
+ if (isCancel(shouldContinue) || !shouldContinue) {
8119
+ cancel("Fix any Flutter issues first, then run create-flutterinit again.");
8120
+ log.message(` ${import_picocolors3.default.cyan("https://flutter.dev/docs/get-started/install")}`);
8121
+ process.exit(0);
8122
+ }
8123
+ }
8124
+
8125
+ // src/prompts.ts
8126
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
8127
+ import path4 from "path";
8128
+
8129
+ // src/config.ts
8130
+ var ARCHITECTURE_LABELS = {
8131
+ clean: "Clean Architecture",
8132
+ mvvm: "MVVM",
8133
+ "feature-first": "Feature-First",
8134
+ mvc: "MVC",
8135
+ "layer-first": "Layer-First"
8136
+ };
8137
+ var STATE_LABELS = {
8138
+ riverpod: "Riverpod",
8139
+ bloc: "Bloc / Cubit",
8140
+ provider: "Provider",
8141
+ mobx: "MobX",
8142
+ getx: "GetX"
8143
+ };
8144
+ var BACKEND_LABELS = {
8145
+ firebase: "Firebase",
8146
+ supabase: "Supabase",
8147
+ appwrite: "Appwrite",
8148
+ custom: "Custom Backend",
8149
+ none: "None"
8150
+ };
8151
+ var NAVIGATION_LABELS = {
8152
+ gorouter: "GoRouter",
8153
+ autoroute: "AutoRoute",
8154
+ none: "Navigator 2.0"
8155
+ };
8156
+ var THEME_LABELS = {
8157
+ light: "Light only",
8158
+ dark: "Dark only",
8159
+ both: "Both (system)"
8160
+ };
8161
+
8162
+ // src/prompts.ts
8163
+ function checkCancel(value) {
8164
+ if (isCancel(value)) {
8165
+ cancel("Cancelled. Run create-flutterinit again whenever you're ready.");
8166
+ process.exit(0);
8167
+ }
8168
+ }
8169
+ async function runPrompts() {
8170
+ printStep("Project Identity", "Basic information about your Flutter application.");
8171
+ const projectName = await text({
8172
+ message: `Project name ${import_picocolors4.default.dim("(lowercase letters, numbers, and underscores)")}`,
8173
+ placeholder: "my_app",
8174
+ validate(value) {
8175
+ if (!value || value.trim().length === 0)
8176
+ return "Project name is required.";
8177
+ if (!/^[a-z][a-z0-9_]*$/.test(value.trim()))
8178
+ return "Must be lowercase with underscores only — e.g. my_app";
8179
+ return;
8586
8180
  }
8587
- s.stop(`${import_picocolors4.default.green("✓")} ${ARCH_FOLDERS[architecture]?.length ?? 0} directories created`);
8588
- } catch (err) {
8589
- s.stop(import_picocolors4.default.red("✗ Folder creation failed"));
8590
- logError(`Failed to create architecture folders: ${err.message}`);
8591
- removeDir(outputDir);
8592
- process.exit(1);
8593
- }
8594
- s.start("Applying architecture templates...");
8595
- try {
8596
- const archTemplateDir = path4.join(TEMPLATE_ROOT, "overlays", "architecture", architecture);
8597
- overlayTemplateDir(archTemplateDir, outputDir, config);
8598
- s.stop(`${import_picocolors4.default.green("✓")} Architecture templates applied`);
8599
- } catch (err) {
8600
- s.stop(import_picocolors4.default.red("✗ Architecture template overlay failed"));
8601
- logError(`Architecture templates failed: ${err.message}`);
8602
- removeDir(outputDir);
8603
- process.exit(1);
8604
- }
8605
- if (backend !== "none") {
8606
- s.start(`Applying ${backend} backend templates...`);
8607
- try {
8608
- const backendTemplateDir = path4.join(TEMPLATE_ROOT, "overlays", "backend", backend);
8609
- overlayTemplateDir(backendTemplateDir, outputDir, config);
8610
- s.stop(`${import_picocolors4.default.green("✓")} ${backend} templates applied`);
8611
- } catch (err) {
8612
- s.stop(import_picocolors4.default.yellow(`⚠ Backend templates had issues — ${err.message}`));
8181
+ });
8182
+ checkCancel(projectName);
8183
+ const name = projectName.trim();
8184
+ const orgName = await text({
8185
+ message: `Organisation name ${import_picocolors4.default.dim("(reverse domain format — e.g. com.yourcompany)")}`,
8186
+ placeholder: "com.example",
8187
+ defaultValue: "com.example",
8188
+ validate(value) {
8189
+ const v = value || "com.example";
8190
+ if (!/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/.test(v))
8191
+ return "Use reverse domain format — e.g. com.example or com.acme.mobile";
8192
+ return;
8613
8193
  }
8614
- }
8615
- if (navigation !== "none") {
8616
- const navName = navigation === "gorouter" ? "go_router" : "auto_route";
8617
- s.start(`Applying ${navName} navigation templates...`);
8618
- try {
8619
- const navTemplateDir = path4.join(TEMPLATE_ROOT, "overlays", "navigation", navName);
8620
- if (fs3.existsSync(navTemplateDir)) {
8621
- overlayTemplateDir(navTemplateDir, outputDir, config);
8194
+ });
8195
+ checkCancel(orgName);
8196
+ const description = await text({
8197
+ message: "Project description",
8198
+ placeholder: "A new Flutter project",
8199
+ defaultValue: "A new Flutter project"
8200
+ });
8201
+ checkCancel(description);
8202
+ printStep("Architecture", "Choose how your project folders and layers are organized.");
8203
+ const architecture = await select({
8204
+ message: "Architecture pattern",
8205
+ options: [
8206
+ {
8207
+ value: "clean",
8208
+ label: "Clean Architecture",
8209
+ hint: "Data → Domain → Presentation layers. Best for large teams."
8210
+ },
8211
+ {
8212
+ value: "mvvm",
8213
+ label: "MVVM",
8214
+ hint: "Model-View-ViewModel. Great with Riverpod or Provider."
8215
+ },
8216
+ {
8217
+ value: "feature-first",
8218
+ label: "Feature-First",
8219
+ hint: "Organized by feature, not layer. Scales well for medium apps."
8220
+ },
8221
+ {
8222
+ value: "mvc",
8223
+ label: "MVC",
8224
+ hint: "Model-View-Controller. Familiar pattern for most developers."
8225
+ },
8226
+ {
8227
+ value: "layer-first",
8228
+ label: "Layer-First",
8229
+ hint: "Global shared layers. Simple, good for smaller apps."
8622
8230
  }
8623
- s.stop(`${import_picocolors4.default.green("✓")} Navigation templates applied`);
8624
- } catch (err) {
8625
- s.stop(import_picocolors4.default.yellow(`⚠ Navigation templates had issues — ${err.message}`));
8231
+ ]
8232
+ });
8233
+ checkCancel(architecture);
8234
+ printStep("State Management", "The state management solution wired throughout your app.");
8235
+ const stateManager = await select({
8236
+ message: "State manager",
8237
+ options: [
8238
+ {
8239
+ value: "riverpod",
8240
+ label: "Riverpod",
8241
+ hint: "AsyncNotifier + Riverpod Generator. Compile-safe & testable."
8242
+ },
8243
+ {
8244
+ value: "bloc",
8245
+ label: "Bloc / Cubit",
8246
+ hint: "Event-driven. Strict separation, excellent for large teams."
8247
+ },
8248
+ {
8249
+ value: "provider",
8250
+ label: "Provider",
8251
+ hint: "Simple InheritedWidget wrapper. Great for smaller apps."
8252
+ },
8253
+ {
8254
+ value: "mobx",
8255
+ label: "MobX",
8256
+ hint: "Reactive observables with code generation."
8257
+ },
8258
+ {
8259
+ value: "getx",
8260
+ label: "GetX",
8261
+ hint: "All-in-one: state, routing, DI. Opinionated but fast."
8262
+ }
8263
+ ]
8264
+ });
8265
+ checkCancel(stateManager);
8266
+ printStep("Backend", "Backend-as-a-service to wire into your data layer.");
8267
+ const backend = await select({
8268
+ message: "Backend service",
8269
+ options: [
8270
+ {
8271
+ value: "firebase",
8272
+ label: "Firebase",
8273
+ hint: "Auth, Firestore, Storage, FCM. Google ecosystem."
8274
+ },
8275
+ {
8276
+ value: "supabase",
8277
+ label: "Supabase",
8278
+ hint: "Open-source Firebase alternative. Postgres + realtime."
8279
+ },
8280
+ {
8281
+ value: "appwrite",
8282
+ label: "Appwrite",
8283
+ hint: "Self-hostable BaaS. Full ownership of your data."
8284
+ },
8285
+ {
8286
+ value: "custom",
8287
+ label: "Custom Backend",
8288
+ hint: "Connect to your own REST API/service via AppConfig."
8289
+ },
8290
+ {
8291
+ value: "none",
8292
+ label: "None",
8293
+ hint: "No backend wired in. Add your own later."
8294
+ }
8295
+ ]
8296
+ });
8297
+ checkCancel(backend);
8298
+ printStep("Navigation", "Routing solution for navigating between screens.");
8299
+ const navigation = await select({
8300
+ message: "Navigation package",
8301
+ options: [
8302
+ {
8303
+ value: "gorouter",
8304
+ label: "GoRouter",
8305
+ hint: "Official Flutter routing. URL-based, deep-link ready."
8306
+ },
8307
+ {
8308
+ value: "autoroute",
8309
+ label: "AutoRoute",
8310
+ hint: "Code-generated typed routes. Zero string-based navigation."
8311
+ },
8312
+ {
8313
+ value: "none",
8314
+ label: "Navigator 2.0",
8315
+ hint: "Vanilla Flutter navigation. No extra dependency."
8316
+ }
8317
+ ]
8318
+ });
8319
+ checkCancel(navigation);
8320
+ printStep("Theme & Appearance", "Material 3 color scheme and theme mode for your app.");
8321
+ const themeMode = await select({
8322
+ message: "Theme mode",
8323
+ options: [
8324
+ {
8325
+ value: "both",
8326
+ label: "Both (system default)",
8327
+ hint: "App respects the device light/dark preference."
8328
+ },
8329
+ {
8330
+ value: "light",
8331
+ label: "Light only",
8332
+ hint: "Always renders in light mode."
8333
+ },
8334
+ {
8335
+ value: "dark",
8336
+ label: "Dark only",
8337
+ hint: "Always renders in dark mode."
8338
+ }
8339
+ ]
8340
+ });
8341
+ checkCancel(themeMode);
8342
+ const primaryColor = await text({
8343
+ message: `Primary color ${import_picocolors4.default.dim("(hex seed color for color scheme)")}`,
8344
+ placeholder: "#027DFD",
8345
+ defaultValue: "#027DFD",
8346
+ validate(value) {
8347
+ const v = value || "#027DFD";
8348
+ if (!/^#[0-9A-Fa-f]{6}$/.test(v))
8349
+ return "Enter a valid 6-digit hex color — e.g. #6750A4";
8350
+ return;
8626
8351
  }
8627
- }
8628
- const ns = spinner();
8629
- ns.start("Configuring native permissions...");
8630
- try {
8631
- await configureNativeFiles(config);
8632
- ns.stop(`${import_picocolors4.default.green("✓")} Native permissions configured`);
8633
- } catch (err) {
8634
- ns.stop(import_picocolors4.default.yellow("⚠ Native configuration skipped — check AndroidManifest.xml and Info.plist manually"));
8635
- logWarn(String(err));
8636
- }
8637
- await trackCliGeneration(config);
8352
+ });
8353
+ checkCancel(primaryColor);
8354
+ printStep("Optional Utilities & Features", "Select additional packages and features to pre-configure in your codebase.");
8355
+ const selectedMiscResult = await groupMultiselect({
8356
+ message: "Select packages to include (Press Space to select & Enter to confirm)",
8357
+ options: {
8358
+ "Icon Packs": [
8359
+ {
8360
+ value: "usesIconsaxPlus",
8361
+ label: "Iconsax Plus",
8362
+ hint: "Modern icon set with 6 distinct styles (linear, bold, etc.)"
8363
+ },
8364
+ {
8365
+ value: "usesFlutterRemix",
8366
+ label: "Flutter Remix",
8367
+ hint: "Remix Icon library package wrapper"
8368
+ },
8369
+ {
8370
+ value: "usesHugeicons",
8371
+ label: "Hugeicons",
8372
+ hint: "Free stroke outline icons pack"
8373
+ }
8374
+ ],
8375
+ "Networking & Storage": [
8376
+ {
8377
+ value: "usesDio",
8378
+ label: "Dio",
8379
+ hint: "Powerful HTTP client with interceptors, form data & caching [Recommended]"
8380
+ },
8381
+ {
8382
+ value: "usesHttp",
8383
+ label: "HTTP Client",
8384
+ hint: "Official lightweight Dart http package"
8385
+ },
8386
+ {
8387
+ value: "usesCachedNetworkImage",
8388
+ label: "Cached Network Image",
8389
+ hint: "Download, render and cache network images automatically [Popular]"
8390
+ },
8391
+ {
8392
+ value: "usesHive",
8393
+ label: "Hive Database",
8394
+ hint: "Lightweight & blazing fast key-value NoSQL database"
8395
+ },
8396
+ {
8397
+ value: "usesSharedPreferences",
8398
+ label: "Shared Preferences",
8399
+ hint: "Platform-persistent key-value pairs storage [Essential]"
8400
+ },
8401
+ {
8402
+ value: "usesSecureStorage",
8403
+ label: "Secure Storage",
8404
+ hint: "Store credentials/sensitive data securely (Keychain/Keystore)"
8405
+ }
8406
+ ],
8407
+ "Media & Assets": [
8408
+ {
8409
+ value: "usesFlutterSvg",
8410
+ label: "Flutter SVG",
8411
+ hint: "Vector SVG rendering support [Popular]"
8412
+ },
8413
+ {
8414
+ value: "usesImagePicker",
8415
+ label: "Image Picker",
8416
+ hint: "Pick images/videos from gallery or shoot new ones with camera"
8417
+ },
8418
+ {
8419
+ value: "usesCamera",
8420
+ label: "Camera",
8421
+ hint: "camera package — full camera control + recording"
8422
+ },
8423
+ {
8424
+ value: "usesFilePicker",
8425
+ label: "File Picker",
8426
+ hint: "Native file explorer to upload files/documents"
8427
+ },
8428
+ {
8429
+ value: "usesFlutterNativeSplash",
8430
+ label: "Flutter Native Splash",
8431
+ hint: "Automatic native splash screens config"
8432
+ }
8433
+ ],
8434
+ "Essential Utilities": [
8435
+ {
8436
+ value: "usesUrlLauncher",
8437
+ label: "URL Launcher",
8438
+ hint: "Trigger browser URLs, map locations, SMS, and telephone calls [Essential]"
8439
+ },
8440
+ {
8441
+ value: "usesPathProvider",
8442
+ label: "Path Provider",
8443
+ hint: "Locate commonly used app folders on device filesystem [Essential]"
8444
+ },
8445
+ {
8446
+ value: "usesSharePlus",
8447
+ label: "Share Plus",
8448
+ hint: "Trigger native system share panels for links, images & text"
8449
+ },
8450
+ {
8451
+ value: "usesPermissionHandler",
8452
+ label: "Permission Handler",
8453
+ hint: "Query and request system hardware permissions dynamically [Essential]"
8454
+ },
8455
+ {
8456
+ value: "usesDeviceInfoPlus",
8457
+ label: "Device Info",
8458
+ hint: "Access deep hardware model/OS version properties"
8459
+ },
8460
+ {
8461
+ value: "usesGeolocator",
8462
+ label: "Geolocator",
8463
+ hint: "Acquire and track device GPS location updates"
8464
+ },
8465
+ {
8466
+ value: "usesNotifications",
8467
+ label: "Local Notifications",
8468
+ hint: "flutter_local_notifications — schedule and display local alerts"
8469
+ },
8470
+ {
8471
+ value: "usesAppVersionUpdate",
8472
+ label: "App Version Update",
8473
+ hint: "Verify and alert users when an app update is available"
8474
+ }
8475
+ ],
8476
+ "Advanced Features": [
8477
+ {
8478
+ value: "usesFlutterHooks",
8479
+ label: "Flutter Hooks",
8480
+ hint: "React-style code structure for widget lifecycle states [Popular]"
8481
+ },
8482
+ {
8483
+ value: "usesSkeletonizer",
8484
+ label: "Skeletonizer",
8485
+ hint: "Transform simple widgets into custom shimmering loader states [UI]"
8486
+ },
8487
+ {
8488
+ value: "usesScreenutil",
8489
+ label: "ScreenUtil",
8490
+ hint: "Sizing & font scaling adapter for responsive layouts [Popular]"
8491
+ },
8492
+ {
8493
+ value: "usesDotenv",
8494
+ label: "Environment Config (.env)",
8495
+ hint: "Pre-configure flutter_dotenv for configuration files support"
8496
+ },
8497
+ {
8498
+ value: "usesLogger",
8499
+ label: "Console Logger",
8500
+ hint: "Logger package for clean output filters"
8501
+ },
8502
+ {
8503
+ value: "useLocalization",
8504
+ label: "Localization (intl)",
8505
+ hint: "Integrate native multi-language supported structures"
8506
+ },
8507
+ {
8508
+ value: "useMaterial3",
8509
+ label: "Material 3 support",
8510
+ hint: "Configure global ThemeData to support M3 guidelines"
8511
+ }
8512
+ ]
8513
+ },
8514
+ required: false,
8515
+ initialValues: [
8516
+ "usesIconsaxPlus",
8517
+ "usesDio",
8518
+ "usesSharedPreferences",
8519
+ "usesSecureStorage",
8520
+ "usesCachedNetworkImage",
8521
+ "usesFlutterSvg",
8522
+ "usesFlutterNativeSplash",
8523
+ "usesUrlLauncher",
8524
+ "usesPathProvider",
8525
+ "usesPermissionHandler",
8526
+ "usesDeviceInfoPlus",
8527
+ "usesAppVersionUpdate",
8528
+ "usesScreenutil",
8529
+ "usesDotenv",
8530
+ "usesLogger",
8531
+ "useLocalization",
8532
+ "useMaterial3"
8533
+ ]
8534
+ });
8535
+ checkCancel(selectedMiscResult);
8536
+ const selectedMisc = selectedMiscResult;
8537
+ const usesCamera = selectedMisc.includes("usesCamera");
8538
+ const usesImagePicker = selectedMisc.includes("usesImagePicker");
8539
+ const usesFilePicker = selectedMisc.includes("usesFilePicker");
8540
+ const usesGeolocator = selectedMisc.includes("usesGeolocator");
8541
+ const usesNotifications = selectedMisc.includes("usesNotifications");
8542
+ const needsPermissionHandler = usesCamera || usesImagePicker || usesFilePicker || usesGeolocator || usesNotifications;
8543
+ const usesPermissionHandler = selectedMisc.includes("usesPermissionHandler") || needsPermissionHandler;
8544
+ const allSelected = [
8545
+ ...selectedMisc.includes("usesIconsaxPlus") ? ["Iconsax Plus"] : [],
8546
+ ...selectedMisc.includes("usesFlutterRemix") ? ["Flutter Remix"] : [],
8547
+ ...selectedMisc.includes("usesHugeicons") ? ["Hugeicons"] : [],
8548
+ ...selectedMisc.includes("usesDio") ? ["Dio"] : [],
8549
+ ...selectedMisc.includes("usesHttp") ? ["HTTP"] : [],
8550
+ ...selectedMisc.includes("usesCachedNetworkImage") ? ["Cached Image"] : [],
8551
+ ...selectedMisc.includes("usesHive") ? ["Hive"] : [],
8552
+ ...selectedMisc.includes("usesSharedPreferences") ? ["SharedPreferences"] : [],
8553
+ ...selectedMisc.includes("usesSecureStorage") ? ["SecureStorage"] : [],
8554
+ ...selectedMisc.includes("usesFlutterSvg") ? ["Flutter SVG"] : [],
8555
+ ...usesImagePicker ? ["Image Picker"] : [],
8556
+ ...usesCamera ? ["Camera"] : [],
8557
+ ...usesFilePicker ? ["File Picker"] : [],
8558
+ ...selectedMisc.includes("usesFlutterNativeSplash") ? ["Native Splash"] : [],
8559
+ ...selectedMisc.includes("usesUrlLauncher") ? ["URL Launcher"] : [],
8560
+ ...selectedMisc.includes("usesPathProvider") ? ["Path Provider"] : [],
8561
+ ...selectedMisc.includes("usesSharePlus") ? ["Share Plus"] : [],
8562
+ ...usesPermissionHandler ? ["Permission Handler"] : [],
8563
+ ...selectedMisc.includes("usesDeviceInfoPlus") ? ["Device Info"] : [],
8564
+ ...usesGeolocator ? ["Geolocator"] : [],
8565
+ ...usesNotifications ? ["Local Notifications"] : [],
8566
+ ...selectedMisc.includes("usesAppVersionUpdate") ? ["App Version Update"] : [],
8567
+ ...selectedMisc.includes("usesFlutterHooks") ? ["Flutter Hooks"] : [],
8568
+ ...selectedMisc.includes("usesSkeletonizer") ? ["Skeletonizer"] : [],
8569
+ ...selectedMisc.includes("usesScreenutil") ? ["ScreenUtil"] : [],
8570
+ ...selectedMisc.includes("usesDotenv") ? ["Dotenv"] : [],
8571
+ ...selectedMisc.includes("usesLogger") ? ["Logger"] : [],
8572
+ ...selectedMisc.includes("useLocalization") ? ["Localization"] : [],
8573
+ ...selectedMisc.includes("useMaterial3") ? ["Material 3"] : []
8574
+ ];
8575
+ const outputDir = path4.resolve(process.cwd(), name);
8576
+ const nativeFeatures = [
8577
+ usesCamera && "Camera",
8578
+ usesImagePicker && "Image Picker",
8579
+ usesFilePicker && "File Picker",
8580
+ usesGeolocator && "Location",
8581
+ usesNotifications && "Notifications"
8582
+ ].filter(Boolean).join(", ");
8638
8583
  note([
8639
- `${import_picocolors4.default.bold("cd")} "${outputDir}"`,
8640
- `${import_picocolors4.default.bold("flutter pub get")}`,
8641
- `${import_picocolors4.default.bold("flutter run")}`,
8584
+ `${import_picocolors4.default.bold("Project")} ${import_picocolors4.default.cyan(name)}`,
8585
+ `${import_picocolors4.default.bold("Org")} ${orgName || "com.example"}`,
8586
+ `${import_picocolors4.default.bold("Description")} ${description || "A new Flutter project"}`,
8642
8587
  ``,
8643
- `${import_picocolors4.default.dim("Or open in VS Code:")}`,
8644
- `${import_picocolors4.default.bold("code")} "${outputDir}"`,
8588
+ `${import_picocolors4.default.bold("Architecture")} ${ARCHITECTURE_LABELS[architecture]}`,
8589
+ `${import_picocolors4.default.bold("State")} ${STATE_LABELS[stateManager]}`,
8590
+ `${import_picocolors4.default.bold("Backend")} ${BACKEND_LABELS[backend]}`,
8591
+ `${import_picocolors4.default.bold("Navigation")} ${NAVIGATION_LABELS[navigation]}`,
8645
8592
  ``,
8646
- `${import_picocolors4.default.dim("Docs & templates:")} ${import_picocolors4.default.cyan("https://flutterinit.com")}`
8593
+ `${import_picocolors4.default.bold("Theme")} ${THEME_LABELS[themeMode]}`,
8594
+ `${import_picocolors4.default.bold("Color")} ${primaryColor || "#027DFD"}`,
8595
+ `${import_picocolors4.default.bold("Utilities")} ${allSelected.length > 0 ? allSelected.join(", ") : "none"}`,
8596
+ `${import_picocolors4.default.bold("Native")} ${nativeFeatures || "None"}`,
8597
+ ``,
8598
+ `${import_picocolors4.default.bold("Output")} ${import_picocolors4.default.dim(outputDir)}`
8647
8599
  ].join(`
8648
- `), "Next steps");
8649
- outro(`${brand(" FlutterInit ")} ${import_picocolors4.default.dim("—")} ${import_picocolors4.default.green("Your Flutter project is ready. Happy coding! \uD83D\uDE80")}`);
8600
+ `), "Your FlutterInit Configuration");
8601
+ const confirmed = await confirm({
8602
+ message: "Generate this project?",
8603
+ initialValue: true
8604
+ });
8605
+ if (isCancel(confirmed) || !confirmed) {
8606
+ cancel("Generation cancelled. Run create-flutterinit again to start over.");
8607
+ process.exit(0);
8608
+ }
8609
+ return {
8610
+ projectName: name,
8611
+ orgName: orgName || "com.example",
8612
+ description: description || "A new Flutter project",
8613
+ architecture,
8614
+ stateManager,
8615
+ backend,
8616
+ navigation,
8617
+ themeMode,
8618
+ primaryColor: primaryColor || "#027DFD",
8619
+ outputDir,
8620
+ usesIconsaxPlus: selectedMisc.includes("usesIconsaxPlus"),
8621
+ usesFlutterRemix: selectedMisc.includes("usesFlutterRemix"),
8622
+ usesHugeicons: selectedMisc.includes("usesHugeicons"),
8623
+ usesDio: selectedMisc.includes("usesDio"),
8624
+ usesHttp: selectedMisc.includes("usesHttp"),
8625
+ usesCachedNetworkImage: selectedMisc.includes("usesCachedNetworkImage"),
8626
+ usesHive: selectedMisc.includes("usesHive"),
8627
+ usesSharedPreferences: selectedMisc.includes("usesSharedPreferences"),
8628
+ usesSecureStorage: selectedMisc.includes("usesSecureStorage"),
8629
+ usesFlutterSvg: selectedMisc.includes("usesFlutterSvg"),
8630
+ usesImagePicker,
8631
+ usesCamera,
8632
+ usesFilePicker,
8633
+ usesFlutterNativeSplash: selectedMisc.includes("usesFlutterNativeSplash"),
8634
+ usesUrlLauncher: selectedMisc.includes("usesUrlLauncher"),
8635
+ usesPathProvider: selectedMisc.includes("usesPathProvider"),
8636
+ usesSharePlus: selectedMisc.includes("usesSharePlus"),
8637
+ usesPermissionHandler,
8638
+ usesDeviceInfoPlus: selectedMisc.includes("usesDeviceInfoPlus"),
8639
+ usesGeolocator,
8640
+ usesNotifications,
8641
+ usesAppVersionUpdate: selectedMisc.includes("usesAppVersionUpdate"),
8642
+ usesFlutterHooks: selectedMisc.includes("usesFlutterHooks"),
8643
+ usesSkeletonizer: selectedMisc.includes("usesSkeletonizer"),
8644
+ usesScreenutil: selectedMisc.includes("usesScreenutil"),
8645
+ usesDotenv: selectedMisc.includes("usesDotenv"),
8646
+ usesLogger: selectedMisc.includes("usesLogger"),
8647
+ useLocalization: selectedMisc.includes("useLocalization"),
8648
+ useMaterial3: selectedMisc.includes("useMaterial3")
8649
+ };
8650
8650
  }
8651
8651
 
8652
8652
  // src/main.ts