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,107 @@
1
+ // bin/commands/killServer.js
2
+ import { execSync } from "child_process";
3
+ import { c, banner, gradient, rainbow } from "../utils/colors.js";
4
+
5
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6
+
7
+ const isAlive = (pid) => {
8
+ try {
9
+ process.kill(pid, 0);
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ };
15
+
16
+ const waitGone = async (pid, timeout) => {
17
+ const start = Date.now();
18
+ while (isAlive(pid) && Date.now() - start < timeout) {
19
+ await sleep(100);
20
+ }
21
+ return !isAlive(pid);
22
+ };
23
+
24
+ const hasLsof = () => {
25
+ try {
26
+ execSync("command -v lsof", { stdio: "pipe" });
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ };
32
+
33
+ const listenersOnPort = (port) => {
34
+ try {
35
+ const out = execSync(`lsof -ti tcp:${port} -sTCP:LISTEN`, {
36
+ encoding: "utf8",
37
+ stdio: ["ignore", "pipe", "ignore"],
38
+ });
39
+ return out.split("\n").map((s) => s.trim()).filter(Boolean);
40
+ } catch {
41
+ return [];
42
+ }
43
+ };
44
+
45
+ export const killServer = async (portArg, { dryRun = false } = {}) => {
46
+ const PORT = Number(portArg) || 8000;
47
+
48
+ console.log(banner("šŸ—”ļø Kill Server", `port ${PORT}`));
49
+
50
+ if (!hasLsof()) {
51
+ console.error(c("red", "āŒ lsof not found (needed to find processes)"));
52
+ console.log(c("gray", ` Try manually: kill $(lsof -ti tcp:${PORT})`));
53
+ process.exit(1);
54
+ }
55
+
56
+ const pids = listenersOnPort(PORT);
57
+
58
+ if (pids.length === 0) {
59
+ console.log(gradient(` āœ… No servers listening on port ${PORT}`, "#34d399", "#22d3ee"));
60
+ return;
61
+ }
62
+
63
+ console.log(
64
+ gradient(`Found ${pids.length} process(es) on port ${PORT}:`, "#fbbf24", "#fb923c"),
65
+ );
66
+ for (const pid of pids) {
67
+ let name = "?";
68
+ try {
69
+ name = execSync(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim();
70
+ } catch {
71
+ // ignore
72
+ }
73
+ console.log(c("gray", ` Ā· ${c("bold", rainbow(pid))} ${c("dim", name)}`));
74
+ }
75
+
76
+ if (dryRun) {
77
+ console.log(
78
+ "\n" + gradient("--dry-run: nothing killed. Run without --dry-run to kill them.", "#22d3ee", "#38bdf8"),
79
+ );
80
+ return;
81
+ }
82
+
83
+ for (const pid of pids) {
84
+ if (!isAlive(pid)) continue;
85
+ try {
86
+ execSync(`kill -TERM ${pid}`, { stdio: "ignore" });
87
+ const gone = await waitGone(pid, 1500);
88
+ if (!gone) {
89
+ execSync(`kill -KILL ${pid}`, { stdio: "ignore" });
90
+ await waitGone(pid, 1000);
91
+ }
92
+ console.log(gradient(` šŸ’€ Killed: ${pid}`, "#f43f5e", "#fb923c"));
93
+ } catch (e) {
94
+ console.error(c("red", `āŒ No se pudo matar ${pid}: ${e && e.message}`));
95
+ }
96
+ }
97
+
98
+ await sleep(300);
99
+ const restantes = listenersOnPort(PORT);
100
+ if (restantes.length > 0) {
101
+ console.log(c("yellow", `āš ļø Processes still on port ${PORT}: ${restantes.join(", ")}`));
102
+ } else {
103
+ console.log(gradient(` āœ… Port ${PORT} released.`, "#34d399", "#22d3ee"));
104
+ }
105
+ };
106
+
107
+ export default killServer;
@@ -0,0 +1,131 @@
1
+ // bin/commands/package.js
2
+ import readline from "readline";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { execCmd } from "../utils/helpers.js";
7
+ import { c, gradient, rainbow, section, divider, panel } from "../utils/colors.js";
8
+ import { execSpin } from "../utils/spinner.js";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ let FRAMEWORK_VERSION = "v1.0.0";
14
+ try {
15
+ FRAMEWORK_VERSION =
16
+ "v" +
17
+ JSON.parse(
18
+ fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"),
19
+ ).version;
20
+ } catch {}
21
+
22
+ export const packageManager = async () => {
23
+ const PACKAGE = "flet-box";
24
+ const PKG_PATH = path.join(__dirname, "../..");
25
+ const rl = readline.createInterface({
26
+ input: process.stdin,
27
+ output: process.stdout,
28
+ });
29
+ const question = (q) => new Promise((resolve) => rl.question(q, resolve));
30
+
31
+ process.on("SIGINT", () => {
32
+ console.log(`\n${rainbow("šŸ‘‹ Bye!")}\n`);
33
+ rl.close();
34
+ process.exit(0);
35
+ });
36
+
37
+ const run = (label, icon, color = "#6366f1") =>
38
+ console.log(`\n${divider(`${icon} ${label}`, color)}`);
39
+
40
+ const opt = (n, name, desc, from = "#34d399", to = "#22d3ee") =>
41
+ ` ${gradient(`${n}.`, from, to)} ${c("bold", name.padEnd(10))}${c("dim", "Ā·")} ${c("gray", desc)}`;
42
+
43
+ while (true) {
44
+ console.clear();
45
+ console.log(`\n${panel("šŸ“¦ FletBox Package Manager", FRAMEWORK_VERSION, { minW: 64 })}`);
46
+ console.log(
47
+ `
48
+ ${section("šŸ”§", "LOCAL DEVELOPMENT")}
49
+ ${opt(1, "link", "Create global link")}
50
+ ${opt(2, "use", "Use link in current project")}
51
+ ${opt(3, "unlink", "Remove the link")}
52
+ ${opt(4, "list", "Show link status")}
53
+
54
+ ${section("šŸ“¦", "NPM REGISTRY")}
55
+ ${opt(5, "publish", "Publish to npm", "#38bdf8", "#6366f1")}
56
+ ${opt(6, "install", "npm install flet-box", "#38bdf8", "#6366f1")}
57
+ ${opt(7, "uninstall", "npm uninstall flet-box", "#38bdf8", "#6366f1")}
58
+
59
+ ${section("šŸ‘¤", "UTILS")}
60
+ ${opt(8, "whoami", "Show npm user", "#d946ef", "#8b5cf6")}
61
+ ${opt(9, "login", "Login to npm", "#d946ef", "#8b5cf6")}
62
+ ${opt(0, "exit", "Leave the manager", "#f43f5e", "#fb923c")}
63
+
64
+ ${divider("", "#3730a3")}
65
+ ${c("gray", " Press Ctrl+C to exit at any time")} ${c("dim", "Ā· " + FRAMEWORK_VERSION)}
66
+ `,
67
+ );
68
+
69
+ const choice = await question(
70
+ `\n ${gradient("āÆ", "#22d3ee", "#a855f7")} ${c("brightCyan", "Select option")}: `,
71
+ );
72
+
73
+ switch (choice) {
74
+ case "1":
75
+ execSpin("Creating global link...", `cd "${PKG_PATH}" && npm link`);
76
+ break;
77
+
78
+ case "2":
79
+ execSpin("Linking flet-box in current project...", `npm link ${PACKAGE}`);
80
+ break;
81
+
82
+ case "3":
83
+ execSpin("Unlinking flet-box...", `npm unlink ${PACKAGE}`);
84
+ break;
85
+
86
+ case "4":
87
+ run("STATUS", "šŸ“‹");
88
+ execCmd(
89
+ `npm list -g --depth=0 2>/dev/null | grep ${PACKAGE} || echo "Not linked globally"`,
90
+ );
91
+ const localPath = path.join(process.cwd(), "node_modules", PACKAGE);
92
+ if (fs.existsSync(localPath)) {
93
+ console.log(` ${gradient("āœ… Installed locally", "#34d399", "#22d3ee")}`);
94
+ } else {
95
+ console.log(` ${c("gray", "āŒ Not installed locally")}`);
96
+ }
97
+ break;
98
+
99
+ case "5":
100
+ execSpin("Publishing to npm...", `cd "${PKG_PATH}" && npm publish`);
101
+ break;
102
+
103
+ case "6":
104
+ execSpin("Installing flet-box from npm...", `npm install ${PACKAGE}`);
105
+ break;
106
+
107
+ case "7":
108
+ execSpin("Uninstalling flet-box...", `npm uninstall ${PACKAGE}`);
109
+ break;
110
+
111
+ case "8":
112
+ run("WHOAMI", "šŸ‘¤");
113
+ execCmd(`npm whoami 2>/dev/null || echo "Not logged in"`);
114
+ break;
115
+
116
+ case "9":
117
+ run("LOGIN", "šŸ”");
118
+ execCmd(`npm adduser`);
119
+ break;
120
+
121
+ case "0":
122
+ console.log(`\n${rainbow("šŸ‘‹ Bye!")}\n`);
123
+ rl.close();
124
+ return;
125
+
126
+ default:
127
+ console.log(`\n ${c("red", "āŒ Invalid option")}`);
128
+ }
129
+ await question(`\n ${c("gray", "Press Enter to continue...")}`);
130
+ }
131
+ };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @file bin/commands/release.js
3
+ * @description `flet-box release` — cut a new OTA release of the current project.
4
+ *
5
+ * A release is the moment the code is considered *stable*: the version is
6
+ * bumped (odometer scheme), the production bundle is rebuilt (which regenerates
7
+ * `www/config.json` with the new version + hash), and the bump is committed and
8
+ * tagged `vX.Y.Z`. When the tag is pushed, CI builds and deploys `www/` to
9
+ * GitHub Pages; running apps then fetch the new `config.json`, notice the hash
10
+ * changed, and self-update — no new store submission required (web layer only).
11
+ *
12
+ * The version increments ONLY here, never on a test/preview build, so that
13
+ * "prueba" builds stay cheap and only a deliberate release moves the number.
14
+ *
15
+ * Usage:
16
+ * flet-box release [--major|--minor|--patch] [dir] [--push]
17
+ *
18
+ * --patch (default) bump PATCH (1.0.0 → 1.0.1)
19
+ * --minor bump MINOR (1.0.0 → 1.1.0)
20
+ * --major bump MAJOR (1.0.0 → 2.0.0)
21
+ * dir bundle target folder (default "www")
22
+ * --push push the commit AND the tag to origin after tagging
23
+ */
24
+
25
+ import { execSync } from "child_process";
26
+ import fs from "fs";
27
+ import path from "path";
28
+ import { c, banner, gradient } from "../utils/colors.js";
29
+ import { createBundle } from "./createBundle.js";
30
+ import { INITIAL_VERSION, incrementVersion, isValidVersion } from "../utils/version.js";
31
+
32
+ /**
33
+ * Runs a git command in `cwd`, returning trimmed stdout. Throws on failure.
34
+ *
35
+ * @param {string} cmd - Git subcommand + args (without the leading "git").
36
+ * @param {string} cwd - Working directory.
37
+ * @returns {string} Trimmed stdout.
38
+ */
39
+ const git = (cmd, cwd) =>
40
+ execSync(`git ${cmd}`, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
41
+
42
+ /**
43
+ * Cuts a new release.
44
+ *
45
+ * @async
46
+ * @param {string[]} argv - Args after the `release` keyword,
47
+ * e.g. `["--minor", "--push"]`.
48
+ * @returns {Promise<void>}
49
+ */
50
+ export const release = async (argv = []) => {
51
+ const projectRoot = process.cwd();
52
+
53
+ // ---- Parse flags --------------------------------------------------------
54
+ let type = "patch";
55
+ let push = false;
56
+ let target = null;
57
+ for (const arg of argv) {
58
+ if (arg === "--major" || arg === "--minor" || arg === "--patch") {
59
+ type = arg.slice(2);
60
+ } else if (arg === "--push") {
61
+ push = true;
62
+ } else if (!arg.startsWith("-") && !target) {
63
+ // First bare positional is the bundle target directory.
64
+ target = arg;
65
+ }
66
+ }
67
+
68
+ console.log(banner("šŸš€ FletBox Release", `--${type}`));
69
+
70
+ // ---- Guard: must be inside a FletBox project ---------------------------
71
+ if (!fs.existsSync(path.join(projectRoot, "src", "app.js"))) {
72
+ console.error(c("red", "āŒ Not inside a FletBox project"));
73
+ console.log(c("gray", "Run this in a project folder with src/app.js (flet-box create <name>)."));
74
+ process.exit(1);
75
+ }
76
+
77
+ // ---- Guard: must be a git repository -----------------------------------
78
+ // The release flow relies on tags so CI can deploy the exact released commit.
79
+ try {
80
+ git("rev-parse --is-inside-work-tree", projectRoot);
81
+ } catch {
82
+ console.error(c("red", "āŒ Not a git repository"));
83
+ console.log(
84
+ c("gray", "Releases are tagged so CI can deploy them. Run: git init && git add -A && git commit -m \"init\""),
85
+ );
86
+ process.exit(1);
87
+ }
88
+
89
+ // ---- Guard: working tree must be clean ---------------------------------
90
+ // A dirty tree means the tag would NOT capture what you think you released
91
+ // (CI checks out the tag = committed state). Force the dev to commit first.
92
+ const dirty = git("status --porcelain", projectRoot);
93
+ if (dirty) {
94
+ console.error(c("red", "āŒ Working tree has uncommitted changes"));
95
+ console.log(c("gray", "Commit or stash them first so the release tag captures exactly this state:"));
96
+ console.log(c("dim", dirty.split("\n").slice(0, 20).map((l) => " " + l).join("\n")));
97
+ process.exit(1);
98
+ }
99
+
100
+ // ---- Bump version in package.json --------------------------------------
101
+ const pkgPath = path.join(projectRoot, "package.json");
102
+ if (!fs.existsSync(pkgPath)) {
103
+ console.error(c("red", "āŒ No package.json found"));
104
+ process.exit(1);
105
+ }
106
+ const pkgRaw = fs.readFileSync(pkgPath, "utf8");
107
+ const pkg = JSON.parse(pkgRaw);
108
+ const current = isValidVersion(pkg.version) ? pkg.version : INITIAL_VERSION;
109
+ const next = incrementVersion(current, type);
110
+
111
+ pkg.version = next;
112
+ // Preserve the file's existing 2-space style + trailing newline.
113
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
114
+ console.log(
115
+ c("blue", "\nšŸ”¢ Version ") +
116
+ c("gray", current) +
117
+ c("blue", " → ") +
118
+ gradient(next, "#34d399", "#22d3ee"),
119
+ );
120
+
121
+ // Safety net: createBundle can hard-exit (e.g. esbuild install failure). If we
122
+ // leave without committing, roll package.json back so the tree isn't left
123
+ // dirty with a version bump for a release that never happened (which would
124
+ // also wedge the clean-tree guard on the next attempt).
125
+ let committed = false;
126
+ process.once("exit", () => {
127
+ if (!committed) {
128
+ try {
129
+ fs.writeFileSync(pkgPath, pkgRaw);
130
+ } catch {
131
+ /* best effort */
132
+ }
133
+ }
134
+ });
135
+
136
+ // ---- Rebuild the bundle (regenerates www/config.json) ------------------
137
+ await createBundle(target);
138
+
139
+ // ---- Commit + tag ------------------------------------------------------
140
+ const tag = `v${next}`;
141
+ try {
142
+ // Stage exactly what the release produced. The clean-tree guard above
143
+ // guarantees the only changes now are the version bump + the freshly built
144
+ // bundle, so `add -A` captures the release faithfully. git still honours
145
+ // .gitignore: a gitignored target (e.g. a generated project's www/) is
146
+ // skipped, leaving only package.json for CI to rebuild from the tag, while a
147
+ // tracked/untracked target is committed so the tag matches what shipped and
148
+ // the tree ends clean (release stays repeatable).
149
+ git("add -A", projectRoot);
150
+ git(`commit -m "chore(release): ${tag}"`, projectRoot);
151
+ git(`tag -a ${tag} -m "Release ${tag}"`, projectRoot);
152
+ committed = true;
153
+ console.log(c("green", `\nāœ… Committed and tagged ${tag}`));
154
+ } catch (error) {
155
+ committed = true; // keep the bump; the message below tells the dev how to finish
156
+ console.error(c("red", "āŒ Git commit/tag failed:"), error.message);
157
+ console.log(
158
+ c("yellow", `āš ļø package.json was bumped to ${next} but the release was not committed. `) +
159
+ c("gray", "Fix the git issue, then: git add package.json && git commit && git tag " + tag),
160
+ );
161
+ process.exit(1);
162
+ }
163
+
164
+ // ---- Optional push -----------------------------------------------------
165
+ if (push) {
166
+ try {
167
+ git("push", projectRoot);
168
+ git(`push origin ${tag}`, projectRoot);
169
+ console.log(c("green", `āœ… Pushed commit + tag ${tag} to origin`));
170
+ } catch (error) {
171
+ console.error(c("red", "āŒ Push failed:"), error.message);
172
+ console.log(c("gray", `Push manually with: git push && git push origin ${tag}`));
173
+ process.exit(1);
174
+ }
175
+ }
176
+
177
+ // ---- Summary -----------------------------------------------------------
178
+ console.log(
179
+ c("gray", `\n šŸ“¦ Bundle: ${gradient((target || "www") + "/", "#8b5cf6", "#d946ef")}`),
180
+ );
181
+ if (!push) {
182
+ console.log(
183
+ c("gray", ` ā¬†ļø Publish it: ${gradient(`git push && git push origin ${tag}`, "#22d3ee", "#38bdf8")}`),
184
+ );
185
+ }
186
+ console.log(
187
+ c("dim", " ā„¹ļø CI deploys www/ to GitHub Pages on the tag; open apps self-update from config.json."),
188
+ );
189
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @file bin/commands/runBundle.js
3
+ * @description Builds the project bundle and immediately serves it for production preview.
4
+ *
5
+ * Combines `createBundle` and `runDevServer` into a single command so developers
6
+ * can verify the production artefact (a single `src/app.js`) without manually
7
+ * running two separate commands. Hot reload is intentionally disabled — the goal
8
+ * is to test exactly what would ship, not to iterate on source.
9
+ */
10
+
11
+ import { createBundle } from "./createBundle.js";
12
+ import { runDevServer } from "./runServer.js";
13
+ import { c, banner, gradient, dot } from "../utils/colors.js";
14
+
15
+ /**
16
+ * Builds the project bundle and serves it with a static dev server.
17
+ *
18
+ * Workflow:
19
+ * 1. Runs `createBundle(target)` to produce the minified output directory.
20
+ * 2. Changes the working directory to the bundle folder.
21
+ * 3. Starts `runDevServer` in SPA mode (all unknown paths fall back to `index.html`)
22
+ * with hot reload disabled so the served content exactly matches the build output.
23
+ *
24
+ * @async
25
+ * @param {object} [options={}] - Configuration options.
26
+ * @param {string} [options.target="www"] - Name of the bundle directory relative
27
+ * to the project root. Passed directly to `createBundle`.
28
+ * @param {number} [options.port=8000] - TCP port the server should listen on.
29
+ * @param {boolean} [options.askPort=false] - When `true`, prompt the user for a port
30
+ * if the chosen one is unavailable.
31
+ * @param {boolean} [options.logRequests=true] - Whether to log each HTTP request to stdout.
32
+ * @returns {Promise<void>}
33
+ */
34
+ export const runBundle = async ({
35
+ target = "www",
36
+ port = 8000,
37
+ askPort = false,
38
+ logRequests = true,
39
+ } = {}) => {
40
+ console.log(banner("šŸš€ Run Bundle", `build + serve ${target}/`));
41
+
42
+ // Step 1: Produce the bundle from the current source tree.
43
+ await createBundle(target);
44
+
45
+ // Step 2: Notify the user that the build succeeded and the server is about to start.
46
+ console.log(
47
+ `\n ${dot("#34d399")} ${c("bold", "Bundle ready")} ${c("gray", target + "/ Ā· SPA Ā· hot reload OFF")}\n`,
48
+ );
49
+
50
+ // Step 3: Serve from inside the bundle directory so relative paths resolve correctly.
51
+ process.chdir(target);
52
+
53
+ await runDevServer({
54
+ spaMode: true, // Enable SPA fallback (index.html for unknown routes).
55
+ hotReload: false, // Hot reload is off — we're previewing the production build.
56
+ port,
57
+ askPort,
58
+ logRequests,
59
+ });
60
+ };
61
+
62
+ export default runBundle;
@@ -0,0 +1,89 @@
1
+ // bin/commands/runCloudflare.js
2
+ import { spawn, spawnSync } from "child_process";
3
+ import { c, banner, divider } from "../utils/colors.js";
4
+ import { spinner } from "../utils/spinner.js";
5
+ import { runDevServer } from "./runServer.js";
6
+
7
+ const TUNNEL_URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
8
+
9
+ /**
10
+ * Starts the local dev server and exposes it through a free Cloudflare quick
11
+ * tunnel (no account required), printing the public HTTPS link to the console.
12
+ *
13
+ * @param {{ port?: number, logRequests?: boolean }} [options]
14
+ * @returns {Promise<void>}
15
+ */
16
+ export const runCloudflareDemo = async (options = {}) => {
17
+ const { port = 8000, logRequests = false } = options;
18
+
19
+ const probe = spawnSync("cloudflared", ["--version"], { encoding: "utf-8" });
20
+ if (probe.error) {
21
+ console.error(c("red", "āŒ cloudflared is not installed"));
22
+ console.log(c("gray", " Install it first:"));
23
+ console.log(c("cyan", " macOS: brew install cloudflared"));
24
+ console.log(
25
+ c("cyan", " Linux: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"),
26
+ );
27
+ process.exit(1);
28
+ }
29
+
30
+ console.log(banner("ā˜ļø FletBox Cloudflare Demo", "public HTTPS tunnel"));
31
+
32
+ await runDevServer({
33
+ spaMode: false,
34
+ hotReload: true,
35
+ port,
36
+ askPort: false,
37
+ logRequests,
38
+ });
39
+
40
+ const boot = spinner("Opening Cloudflare quick tunnel...");
41
+ const tunnel = spawn(
42
+ "cloudflared",
43
+ ["tunnel", "--url", `http://localhost:${port}`],
44
+ { stdio: ["ignore", "pipe", "pipe"] },
45
+ );
46
+
47
+ let announced = false;
48
+
49
+ const announce = (url) => {
50
+ announced = true;
51
+ boot.succeed("Cloudflare tunnel ready");
52
+ console.log("");
53
+ console.log(c("brightCyan", " 🌐 Public link (open this):"));
54
+ console.log(` ${c("bold", c("brightGreen", url))}`);
55
+ console.log("");
56
+ console.log(c("gray", ` Local: http://localhost:${port}`));
57
+ console.log(
58
+ c("gray", " The tunnel stays alive while this process runs. Ctrl+C to stop."),
59
+ );
60
+ console.log(divider("", "#3730a3"));
61
+ };
62
+
63
+ const onData = (chunk) => {
64
+ if (announced) return;
65
+ const match = chunk.toString().match(TUNNEL_URL_RE);
66
+ if (match) announce(match[0]);
67
+ };
68
+
69
+ tunnel.stdout.on("data", onData);
70
+ tunnel.stderr.on("data", onData);
71
+
72
+ tunnel.on("exit", (code) => {
73
+ if (announced) return;
74
+ boot.fail(`Cloudflare tunnel exited (code ${code})`);
75
+ console.log(
76
+ c("gray", " The local server is still running; the tunnel could not start."),
77
+ );
78
+ });
79
+
80
+ // runDevServer's SIGINT handler calls process.exit, so also release the
81
+ // tunnel on the synchronous "exit" event to avoid leaving it orphaned.
82
+ process.on("exit", () => {
83
+ try {
84
+ tunnel.kill("SIGTERM");
85
+ } catch (e) {}
86
+ });
87
+ };
88
+
89
+ export default runCloudflareDemo;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @file bin/commands/runNative.js
3
+ * @description Compiles and launches a FletBox app on an Android emulator or
4
+ * physical device, or on the iOS Simulator, wrapping Capacitor's `cap run`.
5
+ *
6
+ * Usage: `flet-box run-native <android|ios> [--device] [--livereload]`
7
+ */
8
+
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { c, banner } from "../utils/colors.js";
12
+ import {
13
+ assertCapacitorProject,
14
+ ensureNpmDependencies,
15
+ run,
16
+ } from "../utils/capacitor.js";
17
+ import { prepareAndroidEnvironment } from "./buildAndroid.js";
18
+ import { requireAppleToolchain } from "./buildIOS.js";
19
+
20
+ const androidPackages = [
21
+ "@capacitor/android",
22
+ "@capacitor/cli",
23
+ "@capacitor/core",
24
+ "flet-box",
25
+ "sharp",
26
+ ];
27
+
28
+ const iosPackages = [
29
+ "@capacitor/cli",
30
+ "@capacitor/core",
31
+ "@capacitor/ios",
32
+ "flet-box",
33
+ "sharp",
34
+ ];
35
+
36
+ /**
37
+ * Entry point for `flet-box run-native`.
38
+ *
39
+ * @param {string[]} [rawArgs=[]] - Raw CLI args: `<android|ios>` platform plus
40
+ * `--device` (physical device) and `--livereload` (hot reload) flags.
41
+ * @returns {void}
42
+ */
43
+ export const runNative = (rawArgs = []) => {
44
+ const platform = rawArgs[0];
45
+ if (platform !== "android" && platform !== "ios") {
46
+ throw new Error(
47
+ 'Usage: `flet-box run-native <android|ios>` — for example `flet-box run-native android`.',
48
+ );
49
+ }
50
+
51
+ const supportedFlags = ["--device", "--livereload"];
52
+ const unknownFlags = rawArgs.slice(1).filter((arg) => !supportedFlags.includes(arg));
53
+ if (unknownFlags.length > 0) {
54
+ throw new Error(
55
+ `Unknown option for \`run-native ${platform}\`: ${unknownFlags.join(", ")}. Supported options: ${supportedFlags.join(", ")}.`,
56
+ );
57
+ }
58
+ const device = rawArgs.includes("--device");
59
+ const livereload = rawArgs.includes("--livereload");
60
+
61
+ const isAndroid = platform === "android";
62
+ const scripts = isAndroid ? ["android:init", "android:sync"] : ["ios:init", "ios:sync"];
63
+ const packages = isAndroid ? androidPackages : iosPackages;
64
+
65
+ const { projectRoot } = assertCapacitorProject(scripts);
66
+ console.log(
67
+ banner(
68
+ "šŸš€ Flet-Box Native",
69
+ isAndroid
70
+ ? device ? "Android device" : "Android emulator"
71
+ : device ? "iOS device" : "iOS Simulator",
72
+ ),
73
+ );
74
+
75
+ ensureNpmDependencies(projectRoot, packages);
76
+
77
+ let env = process.env;
78
+ if (isAndroid) {
79
+ env = prepareAndroidEnvironment();
80
+ } else {
81
+ requireAppleToolchain({ simulator: !device });
82
+ }
83
+
84
+ const platformDir = path.join(projectRoot, isAndroid ? "android" : "ios");
85
+ if (!fs.existsSync(platformDir)) {
86
+ console.log(c("blue", `\nšŸ“± Initializing the ${isAndroid ? "Android" : "iOS"} platform...`));
87
+ run("npm", ["run", isAndroid ? "android:init" : "ios:init"], projectRoot, `${platform} initialization`, env);
88
+ }
89
+
90
+ console.log(c("blue", "\nšŸ”„ Building and synchronizing the app..."));
91
+ run("npm", ["run", isAndroid ? "android:sync" : "ios:sync"], projectRoot, `${platform} synchronization`, env);
92
+
93
+ const capArgs = ["cap", "run", platform];
94
+ if (device) capArgs.push("--device");
95
+ if (livereload) capArgs.push("--livereload");
96
+
97
+ console.log(
98
+ c("blue", `\nšŸ—ļø Compiling and launching on the ${isAndroid ? "Android" : "iOS"} ${device ? "device" : "emulator/simulator"}...`),
99
+ );
100
+ run("npx", capArgs, projectRoot, `${platform} launch`, env);
101
+
102
+ console.log(
103
+ c(
104
+ "green",
105
+ `\nšŸš€ App launched on the ${isAndroid ? "Android" : "iOS"} ${device ? "device" : "emulator/simulator"}.` +
106
+ `\nšŸ“ Re-run \`flet-box run-native ${platform}\` to rebuild, or add \`--livereload\` for hot reload.`,
107
+ ),
108
+ );
109
+ };