flet-box 0.0.0-stage → 1.0.2

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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. package/src/widgets/index.js +52 -0
@@ -0,0 +1,406 @@
1
+ /**
2
+ * @file bin/commands/create.js
3
+ * @description Scaffolds a new FletBox project from one of several built-in templates.
4
+ *
5
+ * Supported templates:
6
+ * - `basic` — AppBar + Drawer (default)
7
+ * - `blank` — Minimal app with a single screen
8
+ * - `full` — AppBar + Drawer + BottomNavigation
9
+ * - `sidebar` — AppBar + Sidebar (desktop-first)
10
+ * - `adaptive` — Responsive layout that adapts between mobile and desktop
11
+ */
12
+
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import readline from "readline";
16
+ import { fileURLToPath } from "url";
17
+ import { createFile, copyFile, makeExecutable } from "../utils/helpers.js";
18
+ import * as templates from "../utils/templates.js";
19
+ import { parseGithubRepo } from "../utils/bundleConfig.js";
20
+ import { c, banner, gradient } from "../utils/colors.js";
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+
25
+ const capacitorAppId = (projectName) => {
26
+ const segments = projectName
27
+ .toLowerCase()
28
+ .split(/[^a-z0-9]+/)
29
+ .filter(Boolean)
30
+ .map((segment) => (/^[a-z]/.test(segment) ? segment : `app${segment}`));
31
+ return `com.fletbox.${segments.join(".") || "app"}`;
32
+ };
33
+
34
+ /**
35
+ * Parses the raw CLI arguments passed after the project name to determine
36
+ * which template was requested, whether an explicit name was given, and the
37
+ * optional `--repo <user/repo>` for OTA updates.
38
+ *
39
+ * Flag-to-template mapping:
40
+ * - `--adaptive` → `"adaptive"`
41
+ * - `--blank` → `"blank"`
42
+ * - `--full` → `"full"`
43
+ * - `--sidebar` → `"sidebar"`
44
+ * - (none) → `"basic"` (default)
45
+ *
46
+ * Any non-flag argument (does not start with `--`) is treated as the project name.
47
+ * `--repo` consumes the next positional as its value (a GitHub user/repo or URL).
48
+ *
49
+ * @param {string[]} args - Raw CLI arguments, may include flags and a project name.
50
+ * @returns {{ template: string, projectName: string|null, repo: string|null }}
51
+ */
52
+ const parseCreateArgs = (args) => {
53
+ let template = "basic"; // default template
54
+ let projectName = null;
55
+ let repo = null;
56
+ for (let i = 0; i < args.length; i++) {
57
+ if (args[i] === "--adaptive") {
58
+ template = "adaptive";
59
+ } else if (args[i] === "--blank") {
60
+ template = "blank";
61
+ } else if (args[i] === "--full") {
62
+ template = "full";
63
+ } else if (args[i] === "--sidebar") {
64
+ template = "sidebar";
65
+ } else if (args[i] === "--repo") {
66
+ if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
67
+ repo = args[i + 1];
68
+ i++;
69
+ }
70
+ } else if (!args[i].startsWith("--")) {
71
+ projectName = args[i];
72
+ }
73
+ }
74
+ return { template, projectName, repo };
75
+ };
76
+
77
+ /** Asks the user for the OTA repo interactively (TTY only). */
78
+ const askRepo = () =>
79
+ new Promise((resolve) => {
80
+ const rl = readline.createInterface({
81
+ input: process.stdin,
82
+ output: process.stdout,
83
+ });
84
+ rl.question(
85
+ c(
86
+ "cyan",
87
+ "GitHub repo for OTA updates (user/repo or https://... ) [Enter to skip]: ",
88
+ ),
89
+ (answer) => {
90
+ rl.close();
91
+ resolve(answer.trim());
92
+ },
93
+ );
94
+ });
95
+
96
+ /**
97
+ * Normalizes the user's OTA target into a base URL ending in "/".
98
+ * - "user/repo" (or any GitHub URL shape) → GitHub Pages URL.
99
+ * - A full http(s) URL is used as-is.
100
+ * - Unrecognized input → null (the placeholder URL is left in the template).
101
+ *
102
+ * @param {string|null} input - The value from `--repo` or the interactive prompt.
103
+ * @returns {string|null} Base URL ending in "/", or null.
104
+ */
105
+ const resolveOtaUrl = (input) => {
106
+ if (!input) return null;
107
+ const value = input.trim();
108
+ if (/^https?:\/\//i.test(value)) {
109
+ return value.endsWith("/") ? value : `${value}/`;
110
+ }
111
+ const gh = parseGithubRepo(value);
112
+ return gh ? `https://${gh.user}.github.io/${gh.repo}/` : null;
113
+ };
114
+
115
+ /**
116
+ * Creates a new FletBox project in a subdirectory of the current working directory.
117
+ *
118
+ * Steps performed:
119
+ * 1. Validates the project name and checks that the target folder does not exist.
120
+ * 2. Creates the required directory tree (varies by template).
121
+ * 3. Writes all static files: `index.html`, `package.json`, `.gitignore`, etc.
122
+ * 4. Copies bundled Material Icons fonts from the package's own `src/fonts/` directory.
123
+ * 5. Generates screens, layout components, and `src/app.js` based on the chosen template.
124
+ * 6. Makes `run.sh` executable.
125
+ *
126
+ * @async
127
+ * @param {string} projectName - Desired project folder name (validated by the caller).
128
+ * @param {string[]} [rawArgs=[]] - Additional CLI arguments forwarded from the main CLI dispatcher.
129
+ * @returns {Promise<void>}
130
+ */
131
+ export const createProject = async (projectName, rawArgs = []) => {
132
+ const { template, projectName: name, repo: repoArg } = parseCreateArgs([
133
+ projectName,
134
+ ...rawArgs,
135
+ ]);
136
+ const finalName = name || projectName;
137
+ if (!finalName) {
138
+ console.error(c("red", "❌ Project name required"));
139
+ console.log(
140
+ c(
141
+ "gray",
142
+ "Usage: flet-box create <name> [--adaptive|--blank|--full|--sidebar|--repo user/repo]",
143
+ ),
144
+ );
145
+ process.exit(1);
146
+ }
147
+
148
+ // OTA target: asked interactively when --repo is missing and stdin is a TTY.
149
+ // The value becomes update.url in fletbox.config.json (placeholder if skipped).
150
+ let repoInput = repoArg;
151
+ if (!repoInput && process.stdin.isTTY) {
152
+ repoInput = await askRepo();
153
+ }
154
+ const otaUrl = resolveOtaUrl(repoInput);
155
+ if (repoInput && !otaUrl) {
156
+ console.warn(
157
+ c(
158
+ "yellow",
159
+ `⚠️ Could not parse repo "${repoInput}" — leaving the placeholder OTA URL in fletbox.config.json.`,
160
+ ),
161
+ );
162
+ }
163
+
164
+ const appId = capacitorAppId(finalName);
165
+ const projectPath = path.join(process.cwd(), finalName);
166
+ if (fs.existsSync(projectPath)) {
167
+ console.error(c("red", `❌ Folder "${finalName}" already exists`));
168
+ process.exit(1);
169
+ }
170
+
171
+ console.log(banner("📦 Creating project", `${finalName} · ${template}`));
172
+
173
+ // Build the list of directories to create.
174
+ // All templates share a common base; non-blank templates add screens/components.
175
+ const commonDirs = [
176
+ ".github/workflows",
177
+ "assets",
178
+ "scripts",
179
+ "src",
180
+ "src/assets/fonts",
181
+ "src/database",
182
+ ];
183
+ const extraDirs =
184
+ template === "blank" ? [] : ["src/screens", "src/components"];
185
+ if (template === "sidebar" || template === "adaptive") {
186
+ extraDirs.push("src/components/layouts");
187
+ }
188
+ const allDirs = [...commonDirs, ...extraDirs];
189
+ for (const dir of allDirs) {
190
+ const fullPath = path.join(projectPath, dir);
191
+ fs.mkdirSync(fullPath, { recursive: true });
192
+ console.log(`${c("green", "📁")} Created: ${dir}`);
193
+ }
194
+
195
+ // Write files shared by every template.
196
+ createFile(
197
+ path.join(projectPath, "index.html"),
198
+ templates.indexHtml(finalName),
199
+ );
200
+ createFile(
201
+ path.join(projectPath, "package.json"),
202
+ templates.packageJson(finalName),
203
+ );
204
+ createFile(
205
+ path.join(projectPath, "fletbox.config.json"),
206
+ templates.fletboxConfig(otaUrl || undefined),
207
+ );
208
+ createFile(path.join(projectPath, ".gitignore"), templates.gitignore());
209
+ createFile(
210
+ path.join(projectPath, ".github/workflows/build.yml"),
211
+ templates.githubWorkflow(finalName, appId),
212
+ );
213
+ createFile(
214
+ path.join(projectPath, "README.md"),
215
+ templates.readme(finalName, template, appId),
216
+ );
217
+ createFile(path.join(projectPath, "run.sh"), templates.runSh());
218
+ createFile(
219
+ path.join(projectPath, "service-worker.js"),
220
+ templates.serviceWorkerJs(),
221
+ );
222
+ createFile(
223
+ path.join(projectPath, "manifest.json"),
224
+ templates.manifest(finalName),
225
+ );
226
+ createFile(
227
+ path.join(projectPath, "capacitor.config.json"),
228
+ templates.capacitorConfig(finalName, appId),
229
+ );
230
+ createFile(
231
+ path.join(projectPath, "scripts/sync-android-icons.mjs"),
232
+ templates.syncAndroidIconsScript(),
233
+ );
234
+ createFile(
235
+ path.join(projectPath, "scripts/sync-ios-assets.mjs"),
236
+ templates.syncIOSAssetsScript(),
237
+ );
238
+ createFile(
239
+ path.join(projectPath, "src/database/themes.js"),
240
+ templates.themesJs(),
241
+ );
242
+
243
+ // Copy bundled Material Icons fonts from the package's own src/fonts/ directory.
244
+ // The package ships the fonts so projects work offline without an extra install step.
245
+ const sourcePackageDir = path.join(__dirname, "..", "..");
246
+ const sourceFontsDir = path.join(sourcePackageDir, "src/fonts");
247
+ const destFontsDir = path.join(projectPath, "src/assets/fonts");
248
+ if (fs.existsSync(sourceFontsDir)) {
249
+ const iconsCss = path.join(sourceFontsDir, "icons.css");
250
+ const woff = path.join(sourceFontsDir, "MaterialIcons-Regular.woff2");
251
+ if (fs.existsSync(iconsCss))
252
+ copyFile(iconsCss, path.join(destFontsDir, "icons.css"));
253
+ if (fs.existsSync(woff))
254
+ copyFile(woff, path.join(destFontsDir, "MaterialIcons-Regular.woff2"));
255
+ }
256
+
257
+ const sourceAssetsDir = path.join(sourcePackageDir, "src/assets");
258
+ const appAssetsDir = path.join(projectPath, "src/assets");
259
+ for (const icon of [
260
+ "icon-192.png",
261
+ "icon-192-maskable.png",
262
+ "icon-512.png",
263
+ "icon-512-maskable.png",
264
+ ]) {
265
+ const source = path.join(sourceAssetsDir, icon);
266
+ if (!fs.existsSync(source)) {
267
+ throw new Error(`Required Flet-Box app icon is missing: ${source}`);
268
+ }
269
+ copyFile(source, path.join(appAssetsDir, icon));
270
+ }
271
+ const sourceLogo = path.join(sourceAssetsDir, "icon-1024-maskable.png");
272
+ if (!fs.existsSync(sourceLogo)) {
273
+ throw new Error(`Required Capacitor logo is missing: ${sourceLogo}`);
274
+ }
275
+ copyFile(sourceLogo, path.join(projectPath, "assets/logo.png"));
276
+
277
+ // Generate screens and components according to the chosen template.
278
+ if (template === "blank") {
279
+ createFile(path.join(projectPath, "src/app.js"), templates.blankAppJs());
280
+ } else {
281
+ // All non-blank templates share these three starter screens.
282
+ createFile(
283
+ path.join(projectPath, "src/screens/RootScreen.js"),
284
+ templates.rootScreenJs(),
285
+ );
286
+ createFile(
287
+ path.join(projectPath, "src/screens/HomeScreen.js"),
288
+ templates.homeScreenJs(),
289
+ );
290
+ createFile(
291
+ path.join(projectPath, "src/screens/AboutScreen.js"),
292
+ templates.aboutScreenJs(),
293
+ );
294
+ if (template === "adaptive") {
295
+ createFile(
296
+ path.join(projectPath, "src/screens/ProfileScreen.js"),
297
+ templates.profileScreenJs(),
298
+ );
299
+ // Barrel export so app.js can import all screens from one place.
300
+ createFile(
301
+ path.join(projectPath, "src/screens/index.js"),
302
+ templates.screensIndexJs(),
303
+ );
304
+ }
305
+ // Layout components (skipped for blank).
306
+ if (template !== "blank") {
307
+ createFile(
308
+ path.join(projectPath, "src/components/layouts/AppBarComponent.js"),
309
+ templates.appBarComponentJs(),
310
+ );
311
+ if (template === "full" || template === "adaptive") {
312
+ createFile(
313
+ path.join(projectPath, "src/components/layouts/BottomNav.js"),
314
+ templates.bottomNavJs(),
315
+ );
316
+ }
317
+ if (template === "sidebar" || template === "adaptive") {
318
+ createFile(
319
+ path.join(projectPath, "src/components/layouts/Sidebar.js"),
320
+ templates.sidebarJs(),
321
+ );
322
+ }
323
+ // Use the simpler, non-parameterised DrawerMenu for the basic template;
324
+ // all other templates get the modular version that accepts a custom items array.
325
+ if (template === "basic") {
326
+ createFile(
327
+ path.join(projectPath, "src/components/layouts/DrawerMenu.js"),
328
+ templates.drawerMenuJs(),
329
+ );
330
+ } else {
331
+ createFile(
332
+ path.join(projectPath, "src/components/layouts/DrawerMenu.js"),
333
+ templates.drawerMenuModularJs(),
334
+ );
335
+ }
336
+ }
337
+ // Write the template-specific app entry point.
338
+ switch (template) {
339
+ case "basic":
340
+ createFile(
341
+ path.join(projectPath, "src/app.js"),
342
+ templates.basicAppJs(),
343
+ );
344
+ break;
345
+ case "full":
346
+ createFile(path.join(projectPath, "src/app.js"), templates.fullAppJs());
347
+ break;
348
+ case "sidebar":
349
+ createFile(
350
+ path.join(projectPath, "src/app.js"),
351
+ templates.sidebarAppJs(),
352
+ );
353
+ break;
354
+ case "adaptive":
355
+ createFile(
356
+ path.join(projectPath, "src/app.js"),
357
+ templates.adaptiveAppJs(),
358
+ );
359
+ break;
360
+ default:
361
+ createFile(
362
+ path.join(projectPath, "src/app.js"),
363
+ templates.basicAppJs(),
364
+ );
365
+ }
366
+ }
367
+
368
+ makeExecutable(path.join(projectPath, "run.sh"));
369
+
370
+ console.log(
371
+ `\n${gradient(` ✅ Project "${finalName}" created successfully!`, "#34d399", "#22d3ee")}\n`,
372
+ );
373
+ console.log(` ${c("brightCyan", "cd")} ${finalName}`);
374
+ console.log(` ${c("brightCyan", "npm install")}`);
375
+ console.log(` ${c("brightCyan", "npm run dev")}\n`);
376
+ console.log(`${c("gray", `📱 Template: ${template}`)}`);
377
+ if (otaUrl) {
378
+ console.log(`${c("gray", `⬆️ OTA updates: ${gradient(otaUrl, "#22d3ee", "#38bdf8")}`)}`);
379
+ } else {
380
+ console.log(
381
+ c(
382
+ "gray",
383
+ `⬆️ OTA: placeholder set — edit fletbox.config.json and replace TU-USUARIO/TU-REPO with your public GitHub repo.\n`,
384
+ ),
385
+ );
386
+ }
387
+ console.log(
388
+ c(
389
+ "gray",
390
+ "Android: npm run android:init && npm run android:sync && npm run android:open\n",
391
+ ),
392
+ );
393
+ console.log(
394
+ c(
395
+ "gray",
396
+ "iOS: npm install && flet-box build ios (macOS + Xcode + CocoaPods)\n",
397
+ ),
398
+ );
399
+ console.log(
400
+ c(
401
+ "gray",
402
+ "CI: .github/workflows/build.yml builds web + Android + iOS on every push.\n" +
403
+ " Enable GitHub Pages (Settings → Pages → Source: GitHub Actions) for the web preview.\n",
404
+ ),
405
+ );
406
+ };