create-flutterinit 0.1.11 → 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.
- package/bin/index.ts +3 -0
- package/dist/index.js +923 -923
- 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(
|
|
1241
|
-
return /^\.|this\b/.test(
|
|
1240
|
+
scopedId: function scopedId(path) {
|
|
1241
|
+
return /^\.|this\b/.test(path.original);
|
|
1242
1242
|
},
|
|
1243
|
-
simpleId: function simpleId(
|
|
1244
|
-
return
|
|
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(
|
|
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
|
|
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),
|
|
2621
|
+
var params = this.setupFullMustacheParams(decorator, program, undefined), path = decorator.path;
|
|
2622
2622
|
this.useDecorators = true;
|
|
2623
|
-
this.opcode("registerDecorator", params.length,
|
|
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
|
|
2687
|
-
this.opcode("getContext",
|
|
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
|
-
|
|
2691
|
-
this.accept(
|
|
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
|
|
2696
|
-
|
|
2697
|
-
this.accept(
|
|
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),
|
|
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
|
-
|
|
2708
|
-
|
|
2709
|
-
this.accept(
|
|
2710
|
-
this.opcode("invokeHelper", params.length,
|
|
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(
|
|
2714
|
-
this.addDepth(
|
|
2715
|
-
this.opcode("getContext",
|
|
2716
|
-
var 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,
|
|
2718
|
+
this.opcode("lookupBlockParam", blockParamId, path.parts);
|
|
2719
2719
|
} else if (!name) {
|
|
2720
2720
|
this.opcode("pushContext");
|
|
2721
|
-
} else if (
|
|
2721
|
+
} else if (path.data) {
|
|
2722
2722
|
this.options.data = true;
|
|
2723
|
-
this.opcode("lookupData",
|
|
2723
|
+
this.opcode("lookupData", path.depth, path.parts, path.strict);
|
|
2724
2724
|
} else {
|
|
2725
|
-
this.opcode("lookupOnContext",
|
|
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
|
|
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
|
-
|
|
3075
|
+
path = url.path;
|
|
3076
3076
|
}
|
|
3077
|
-
var isAbsolute = exports.isAbsolute(
|
|
3078
|
-
var parts =
|
|
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
|
-
|
|
3096
|
-
if (
|
|
3097
|
-
|
|
3095
|
+
path = parts.join("/");
|
|
3096
|
+
if (path === "") {
|
|
3097
|
+
path = isAbsolute ? "/" : ".";
|
|
3098
3098
|
}
|
|
3099
3099
|
if (url) {
|
|
3100
|
-
url.path =
|
|
3100
|
+
url.path = path;
|
|
3101
3101
|
return urlGenerate(url);
|
|
3102
3102
|
}
|
|
3103
|
-
return
|
|
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
|
|
5663
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
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/
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
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
|
|
7373
|
-
|
|
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
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
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
|
-
|
|
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
|
|
7420
|
-
const
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
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
|
|
7427
|
-
|
|
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
|
|
7430
|
-
|
|
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
|
|
7433
|
-
|
|
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
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
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
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
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
|
-
|
|
7461
|
-
logWarn("Could not detect Bun version. Continuing anyway.");
|
|
7477
|
+
result.push(line);
|
|
7462
7478
|
}
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
const
|
|
7473
|
-
message:
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
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/
|
|
7484
|
-
var
|
|
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 =
|
|
7510
|
+
var __dirname2 = path.dirname(__filename2);
|
|
8158
7511
|
var TEMPLATE_ROOT = (() => {
|
|
8159
|
-
const fromSrc =
|
|
7512
|
+
const fromSrc = path.resolve(__dirname2, "../../templates/flutter");
|
|
8160
7513
|
if (fs.existsSync(fromSrc))
|
|
8161
7514
|
return fromSrc;
|
|
8162
|
-
const fromDist =
|
|
7515
|
+
const fromDist = path.resolve(__dirname2, "../templates");
|
|
8163
7516
|
if (fs.existsSync(fromDist))
|
|
8164
7517
|
return fromDist;
|
|
8165
|
-
return
|
|
7518
|
+
return path.resolve(process.cwd(), "templates");
|
|
8166
7519
|
})();
|
|
8167
|
-
var PARTIALS_ROOT =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
7948
|
+
const srcPath = path3.join(templateDir, entry.name);
|
|
8523
7949
|
if (entry.isDirectory()) {
|
|
8524
|
-
const destDir =
|
|
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 =
|
|
8532
|
-
const relPath =
|
|
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 =
|
|
8540
|
-
fs3.mkdirSync(
|
|
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: ${
|
|
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(`${
|
|
7989
|
+
s.stop(`${import_picocolors2.default.green("✓")} Flutter project scaffolded`);
|
|
8564
7990
|
} catch (err) {
|
|
8565
|
-
s.stop(
|
|
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 =
|
|
7998
|
+
const baseTemplateDir = path3.join(TEMPLATE_ROOT, "base");
|
|
8573
7999
|
overlayTemplateDir(baseTemplateDir, outputDir, config);
|
|
8574
|
-
s.stop(`${
|
|
8000
|
+
s.stop(`${import_picocolors2.default.green("✓")} Base templates applied`);
|
|
8575
8001
|
} catch (err) {
|
|
8576
|
-
s.stop(
|
|
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(
|
|
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
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
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
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
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
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
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
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
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("
|
|
8640
|
-
`${import_picocolors4.default.bold("
|
|
8641
|
-
`${import_picocolors4.default.bold("
|
|
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.
|
|
8644
|
-
`${import_picocolors4.default.bold("
|
|
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.
|
|
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
|
-
`), "
|
|
8649
|
-
|
|
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
|