flet-box 0.0.0-stage → 1.0.1

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 (179) 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/package.json +45 -0
  19. package/bin/utils/bundleConfig.js +178 -0
  20. package/bin/utils/capacitor.js +137 -0
  21. package/bin/utils/colors.js +215 -0
  22. package/bin/utils/helpers.js +80 -0
  23. package/bin/utils/spinner.js +96 -0
  24. package/bin/utils/templates.js +1823 -0
  25. package/bin/utils/version.js +108 -0
  26. package/package.json +54 -4
  27. package/src/animations/AnimatedBox.js +607 -0
  28. package/src/animations/AnimatedText.js +105 -0
  29. package/src/animations/MatrixRain.js +220 -0
  30. package/src/animations/ParallaxBox.js +369 -0
  31. package/src/animations/animate.js +332 -0
  32. package/src/animations/index.js +25 -0
  33. package/src/app.js +41 -0
  34. package/src/assets/icon-1024-maskable.png +0 -0
  35. package/src/assets/icon-192-maskable.png +0 -0
  36. package/src/assets/icon-192.png +0 -0
  37. package/src/assets/icon-512-maskable.png +0 -0
  38. package/src/assets/icon-512.png +0 -0
  39. package/src/assets/logo.png +0 -0
  40. package/src/core/App.js +44 -0
  41. package/src/core/hmr-client.js +142 -0
  42. package/src/core/index.js +34 -0
  43. package/src/core/pwa.js +100 -0
  44. package/src/core/runApp.js +430 -0
  45. package/src/create-index.sh +237 -0
  46. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  47. package/src/fonts/icons.css +23 -0
  48. package/src/index.d.ts +1930 -0
  49. package/src/index.js +288 -0
  50. package/src/navigations/AdaptiveScaffold.js +149 -0
  51. package/src/navigations/AppBar.js +331 -0
  52. package/src/navigations/BottomNavigation.js +298 -0
  53. package/src/navigations/CollapsibleSideBar.js +139 -0
  54. package/src/navigations/Drawer.js +333 -0
  55. package/src/navigations/DrawerItem.js +260 -0
  56. package/src/navigations/Router.js +475 -0
  57. package/src/navigations/Scaffold.js +537 -0
  58. package/src/navigations/Tabs.js +384 -0
  59. package/src/navigations/index.js +57 -0
  60. package/src/services/RamStore.js +213 -0
  61. package/src/services/Session.js +310 -0
  62. package/src/services/Storage.js +309 -0
  63. package/src/services/http.js +350 -0
  64. package/src/services/index.js +57 -0
  65. package/src/tools/animation.js +42 -0
  66. package/src/tools/array.js +95 -0
  67. package/src/tools/border.js +28 -0
  68. package/src/tools/clipboard.js +104 -0
  69. package/src/tools/color.js +329 -0
  70. package/src/tools/createList.js +215 -0
  71. package/src/tools/delay.js +55 -0
  72. package/src/tools/device.js +138 -0
  73. package/src/tools/dict.js +297 -0
  74. package/src/tools/dimensions.js +118 -0
  75. package/src/tools/filter.js +41 -0
  76. package/src/tools/flex.js +26 -0
  77. package/src/tools/gradient.js +34 -0
  78. package/src/tools/grid.js +57 -0
  79. package/src/tools/index.js +38 -0
  80. package/src/tools/mapList.js +107 -0
  81. package/src/tools/margin.js +93 -0
  82. package/src/tools/memo.js +60 -0
  83. package/src/tools/os.js +104 -0
  84. package/src/tools/padding.js +93 -0
  85. package/src/tools/print.js +69 -0
  86. package/src/tools/random.js +299 -0
  87. package/src/tools/ref.js +70 -0
  88. package/src/tools/rgba.js +4 -0
  89. package/src/tools/shadow.js +23 -0
  90. package/src/tools/string.js +85 -0
  91. package/src/tools/time.js +674 -0
  92. package/src/tools/transform.js +68 -0
  93. package/src/tools/transition.js +48 -0
  94. package/src/tools/useState.js +268 -0
  95. package/src/tools/uuid.js +42 -0
  96. package/src/utils/TextInputValidator.js +256 -0
  97. package/src/utils/composeUpdate.js +65 -0
  98. package/src/utils/getWidgetProps.js +35 -0
  99. package/src/utils/index.js +76 -0
  100. package/src/utils/markdownParser.js +368 -0
  101. package/src/utils/mediaTime.js +79 -0
  102. package/src/utils/navigation.js +263 -0
  103. package/src/utils/scriptInjector.js +73 -0
  104. package/src/utils/stackPosition.js +31 -0
  105. package/src/utils/stopWebRefresh.js +77 -0
  106. package/src/utils/styleInjector.js +38 -0
  107. package/src/utils/syntaxHighlight.js +339 -0
  108. package/src/utils/themes.js +347 -0
  109. package/src/utils/units.js +87 -0
  110. package/src/utils/visualEffects.js +169 -0
  111. package/src/widget-factory/addChildren.js +114 -0
  112. package/src/widget-factory/assignProps.js +256 -0
  113. package/src/widget-factory/createWidget.js +28 -0
  114. package/src/widget-factory/effects.js +132 -0
  115. package/src/widget-factory/index.js +7 -0
  116. package/src/widget-factory/lifecycle.js +210 -0
  117. package/src/widget-factory/makeParentable.js +89 -0
  118. package/src/widget-factory/pixTool.js +40 -0
  119. package/src/widget-factory/processProps.js +122 -0
  120. package/src/widget-factory/reactivity.js +98 -0
  121. package/src/widget-factory/remTool.js +34 -0
  122. package/src/widget-factory/stackPosition.js +87 -0
  123. package/src/widget-factory/stringTool.js +26 -0
  124. package/src/widget-factory/tools.js +249 -0
  125. package/src/widget-factory/translateProps.js +1049 -0
  126. package/src/widget-factory/warnings.js +284 -0
  127. package/src/widget-factory/widgetFactory.js +200 -0
  128. package/src/widgets/Accordion.js +562 -0
  129. package/src/widgets/AdMob.js +315 -0
  130. package/src/widgets/AdSense.js +268 -0
  131. package/src/widgets/AlertDialog.js +162 -0
  132. package/src/widgets/Audio.js +311 -0
  133. package/src/widgets/Avatar.js +210 -0
  134. package/src/widgets/Badge.js +189 -0
  135. package/src/widgets/BottomSheet.js +287 -0
  136. package/src/widgets/Button.js +247 -0
  137. package/src/widgets/Card.js +33 -0
  138. package/src/widgets/Carousel.js +301 -0
  139. package/src/widgets/Chart.js +632 -0
  140. package/src/widgets/Checkbox.js +106 -0
  141. package/src/widgets/Chip.js +153 -0
  142. package/src/widgets/CircularBar.js +352 -0
  143. package/src/widgets/CircularChart.js +549 -0
  144. package/src/widgets/CodeViewer.js +236 -0
  145. package/src/widgets/Column.js +29 -0
  146. package/src/widgets/Container.js +31 -0
  147. package/src/widgets/DataTable.js +257 -0
  148. package/src/widgets/Divider.js +93 -0
  149. package/src/widgets/DraggBox.js +115 -0
  150. package/src/widgets/Dropdown.js +505 -0
  151. package/src/widgets/DroppBox.js +180 -0
  152. package/src/widgets/FloatingActionButton.js +91 -0
  153. package/src/widgets/GridView.js +20 -0
  154. package/src/widgets/Icon.js +33 -0
  155. package/src/widgets/Image.js +15 -0
  156. package/src/widgets/Input.js +565 -0
  157. package/src/widgets/Inspector.js +254 -0
  158. package/src/widgets/InstallButton.js +110 -0
  159. package/src/widgets/ListTile.js +224 -0
  160. package/src/widgets/ListView.js +503 -0
  161. package/src/widgets/Markdown.js +1236 -0
  162. package/src/widgets/Modal.js +496 -0
  163. package/src/widgets/Pagination.js +195 -0
  164. package/src/widgets/ProgressBar.js +178 -0
  165. package/src/widgets/QRCode.js +467 -0
  166. package/src/widgets/Radio.js +132 -0
  167. package/src/widgets/Rating.js +261 -0
  168. package/src/widgets/Row.js +29 -0
  169. package/src/widgets/Skeleton.js +243 -0
  170. package/src/widgets/Slider.js +527 -0
  171. package/src/widgets/SnackBar.js +227 -0
  172. package/src/widgets/Stack.js +29 -0
  173. package/src/widgets/Stepper.js +296 -0
  174. package/src/widgets/Switch.js +102 -0
  175. package/src/widgets/Text.js +98 -0
  176. package/src/widgets/Tooltip.js +267 -0
  177. package/src/widgets/TreeView.js +370 -0
  178. package/src/widgets/Video.js +92 -0
  179. package/src/widgets/index.js +52 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @file bin/utils/capacitor.js
3
+ * @description Shared plumbing for the Capacitor build commands (`build android`,
4
+ * `build ios`). Both commands do the same three things before they touch a
5
+ * native toolchain: validate the project, make sure the generated npm
6
+ * dependencies exist, and run child processes with readable failures.
7
+ */
8
+
9
+ import { spawnSync } from "node:child_process";
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { c } from "./colors.js";
13
+
14
+ /**
15
+ * Runs a command synchronously, inheriting stdio so the user watches Gradle,
16
+ * CocoaPods or Xcode do their work.
17
+ *
18
+ * @param {string} command - Executable to run.
19
+ * @param {string[]} args - Arguments for the executable.
20
+ * @param {string} cwd - Working directory.
21
+ * @param {string} label - Human readable name used in error messages.
22
+ * @param {NodeJS.ProcessEnv} [env] - Environment for the child process.
23
+ * @returns {void}
24
+ * @throws {Error} When the command cannot start or exits with a non-zero code.
25
+ */
26
+ export const run = (command, args, cwd, label, env = process.env) => {
27
+ const result = spawnSync(command, args, {
28
+ cwd,
29
+ stdio: "inherit",
30
+ shell: process.platform === "win32",
31
+ env,
32
+ });
33
+
34
+ if (result.error) {
35
+ throw new Error(`${label} could not start: ${result.error.message}`);
36
+ }
37
+ if (result.status !== 0) {
38
+ throw new Error(
39
+ `${label} failed with exit code ${result.status ?? "unknown"}`,
40
+ );
41
+ }
42
+ };
43
+
44
+ /**
45
+ * Checks that a command exists and can report a version.
46
+ *
47
+ * @param {string} command - Executable name, resolved through PATH.
48
+ * @param {string[]} [args] - Arguments used as the probe (defaults to `--version`).
49
+ * @returns {boolean} `true` when the probe exits successfully.
50
+ */
51
+ export const commandExists = (command, args = ["--version"]) => {
52
+ const result = spawnSync(command, args, { stdio: "ignore" });
53
+ return !result.error && result.status === 0;
54
+ };
55
+
56
+ const hasPackage = (projectRoot, packageName) =>
57
+ fs.existsSync(
58
+ path.join(projectRoot, "node_modules", ...packageName.split("/"), "package.json"),
59
+ );
60
+
61
+ /**
62
+ * Installs the project npm dependencies when any of the packages the native
63
+ * build needs are missing. Projects created by `flet-box create` already declare
64
+ * them, this only repairs projects whose `node_modules` was never installed.
65
+ *
66
+ * @param {string} projectRoot - Absolute path of the project.
67
+ * @param {string[]} requiredPackages - Packages that must be resolvable.
68
+ * @returns {void}
69
+ */
70
+ export const ensureNpmDependencies = (projectRoot, requiredPackages) => {
71
+ const missingPackages = requiredPackages.filter(
72
+ (packageName) => !hasPackage(projectRoot, packageName),
73
+ );
74
+ if (missingPackages.length === 0) return;
75
+
76
+ console.log(
77
+ c(
78
+ "yellow",
79
+ `\n📦 Installing project dependencies: ${missingPackages.join(", ")}`,
80
+ ),
81
+ );
82
+ run("npm", ["install"], projectRoot, "npm dependency installation");
83
+
84
+ const stillMissing = requiredPackages.filter(
85
+ (packageName) => !hasPackage(projectRoot, packageName),
86
+ );
87
+ if (stillMissing.length > 0) {
88
+ throw new Error(
89
+ `Dependencies are still missing after npm install: ${stillMissing.join(", ")}.`,
90
+ );
91
+ }
92
+ };
93
+
94
+ /**
95
+ * Validates that the current directory is a FletBox project scaffolded for
96
+ * Capacitor, and returns its parsed `package.json`.
97
+ *
98
+ * @param {string[]} requiredScripts - npm scripts the caller needs, for example `["ios:init"]`.
99
+ * @returns {{ projectRoot: string, projectPackage: object }} Project root and manifest.
100
+ * @throws {Error} When the folder is not a Capacitor-enabled FletBox project.
101
+ */
102
+ export const assertCapacitorProject = (requiredScripts) => {
103
+ const projectRoot = process.cwd();
104
+ const packagePath = path.join(projectRoot, "package.json");
105
+ const capacitorConfig = [
106
+ "capacitor.config.json",
107
+ "capacitor.config.ts",
108
+ "capacitor.config.js",
109
+ ].some((file) => fs.existsSync(path.join(projectRoot, file)));
110
+
111
+ if (!fs.existsSync(path.join(projectRoot, "src", "app.js"))) {
112
+ throw new Error("Run this inside a Flet-Box project with src/app.js.");
113
+ }
114
+ if (!fs.existsSync(packagePath) || !capacitorConfig) {
115
+ throw new Error(
116
+ "This project is not configured for Capacitor. Create it with a recent version of Flet-Box.",
117
+ );
118
+ }
119
+
120
+ let projectPackage;
121
+ try {
122
+ projectPackage = JSON.parse(fs.readFileSync(packagePath, "utf8"));
123
+ } catch (error) {
124
+ throw new Error(`Could not read package.json: ${error.message}`);
125
+ }
126
+
127
+ const missingScripts = requiredScripts.filter(
128
+ (script) => !projectPackage?.scripts?.[script],
129
+ );
130
+ if (missingScripts.length > 0) {
131
+ throw new Error(
132
+ `Missing npm scripts: ${missingScripts.join(", ")}. Recreate the project with a recent version of Flet-Box.`,
133
+ );
134
+ }
135
+
136
+ return { projectRoot, projectPackage };
137
+ };
@@ -0,0 +1,215 @@
1
+ // bin/utils/colors.js
2
+ // Visual system of the CLI: ANSI + truecolor (gradients) with safe fallbacks.
3
+
4
+ const TRUE_COLOR = (() => {
5
+ if (process.env.COLORTERM === "truecolor") return true;
6
+ return /iTerm|vscode|kitty|ghostty|hyper|alacritty|wezterm|contour|tmux/i.test(
7
+ process.env.TERM_PROGRAM || "",
8
+ );
9
+ })();
10
+
11
+ // Colors only if there is a TTY and they weren't disabled (NO_COLOR, TERM=dumb)
12
+ const enabled = (() => {
13
+ if (process.env.NO_COLOR) return false;
14
+ if (process.env.TERM === "dumb") return false;
15
+ try {
16
+ return Boolean(process.stdout && process.stdout.isTTY);
17
+ } catch {
18
+ return false;
19
+ }
20
+ })();
21
+
22
+ const code = (x) => (enabled ? x : "");
23
+ const reset = () => code("\x1b[0m");
24
+
25
+ export const colors = {
26
+ // Standard
27
+ black: code("\x1b[30m"),
28
+ red: code("\x1b[31m"),
29
+ green: code("\x1b[32m"),
30
+ yellow: code("\x1b[33m"),
31
+ blue: code("\x1b[34m"),
32
+ magenta: code("\x1b[35m"),
33
+ cyan: code("\x1b[36m"),
34
+ white: code("\x1b[37m"),
35
+ gray: code("\x1b[90m"),
36
+ // Bright
37
+ brightRed: code("\x1b[91m"),
38
+ brightGreen: code("\x1b[92m"),
39
+ brightYellow: code("\x1b[93m"),
40
+ brightBlue: code("\x1b[94m"),
41
+ brightMagenta: code("\x1b[95m"),
42
+ brightCyan: code("\x1b[96m"),
43
+ brightWhite: code("\x1b[97m"),
44
+ // Styles
45
+ bold: code("\x1b[1m"),
46
+ dim: code("\x1b[2m"),
47
+ italic: code("\x1b[3m"),
48
+ underline: code("\x1b[4m"),
49
+ };
50
+
51
+ export const c = (color, text) => {
52
+ const colorCode = colors[color];
53
+ if (colorCode == null) return text;
54
+ return `${colorCode}${text}${reset()}`;
55
+ };
56
+
57
+ // ---------- Truecolor ----------
58
+
59
+ const rgb = (r, g, b) => code(`\x1b[38;2;${r};${g};${b}m`);
60
+ const hex2rgb = (hex) => {
61
+ let h = (hex || "#6366f1").replace("#", "");
62
+ if (h.length === 3) h = h.split("").map((x) => x + x).join("");
63
+ return [
64
+ parseInt(h.slice(0, 2), 16) || 0,
65
+ parseInt(h.slice(2, 4), 16) || 0,
66
+ parseInt(h.slice(4, 6), 16) || 0,
67
+ ];
68
+ };
69
+
70
+ // ANSI fallback for terminals without truecolor (e.g. Terminal.app)
71
+ const ANSI_FALLBACK = ["\x1b[36m", "\x1b[96m", "\x1b[35m", "\x1b[95m"];
72
+ const paintFallback = (text) => {
73
+ const chars = [...text];
74
+ let out = "";
75
+ for (let i = 0; i < chars.length; i++) {
76
+ out += ANSI_FALLBACK[i % ANSI_FALLBACK.length] + chars[i];
77
+ }
78
+ return out + reset();
79
+ };
80
+
81
+ export const hex = (text, hexColor = "#6366f1") => {
82
+ if (!enabled || !TRUE_COLOR) return text;
83
+ const [r, g, b] = hex2rgb(hexColor);
84
+ return `${rgb(r, g, b)}${text}${reset()}`;
85
+ };
86
+
87
+ export const gradient = (text, from = "#6366f1", to = "#d946ef") => {
88
+ if (!enabled || text.length === 0) return text;
89
+ if (!TRUE_COLOR) return paintFallback(text);
90
+ const [f1, f2, f3] = hex2rgb(from);
91
+ const [t1, t2, t3] = hex2rgb(to);
92
+ const chars = [...text];
93
+ const N = Math.max(chars.length - 1, 1);
94
+ let out = "";
95
+ for (let i = 0; i < chars.length; i++) {
96
+ out +=
97
+ rgb(
98
+ Math.round(f1 + ((t1 - f1) * i) / N),
99
+ Math.round(f2 + ((t2 - f2) * i) / N),
100
+ Math.round(f3 + ((t3 - f3) * i) / N),
101
+ ) + chars[i];
102
+ }
103
+ return out + reset();
104
+ };
105
+
106
+ const PALETTE = [
107
+ "#6366f1",
108
+ "#8b5cf6",
109
+ "#d946ef",
110
+ "#ec4899",
111
+ "#f43f5e",
112
+ "#fb923c",
113
+ "#fbbf24",
114
+ "#34d399",
115
+ "#22d3ee",
116
+ "#6366f1",
117
+ ];
118
+
119
+ export const rainbow = (text) => {
120
+ if (!enabled || text.length === 0) return text;
121
+ if (!TRUE_COLOR) return paintFallback(text);
122
+ const chars = [...text];
123
+ const N = chars.length;
124
+ let out = "";
125
+ for (let i = 0; i < N; i++) {
126
+ const [r, g, b] = hex2rgb(PALETTE[Math.floor((i / N) * (PALETTE.length - 1))]);
127
+ out += rgb(r, g, b) + chars[i];
128
+ }
129
+ return out + reset();
130
+ };
131
+
132
+ // ---------- Layout helpers ----------
133
+
134
+ const strip = (t) => t.replace(/\x1b\[[0-9;]*m/g, "");
135
+ export const width = (t) => [...strip(t)].length;
136
+
137
+ // Display length: emoji / wide glyphs count as 2 cells
138
+ const dispLen = (t) =>
139
+ [...strip(t)].reduce(
140
+ (n, ch) => n + (ch.codePointAt(0) >= 0x1100 ? 2 : 1),
141
+ 0,
142
+ );
143
+
144
+ export const divider = (text = "", color = "#8b5cf6") => {
145
+ const line = "─".repeat(Math.max(2, 56 - width(text)));
146
+ if (!text) return hex(line, color);
147
+ return hex(line, color) + " " + text + " " + hex(line, color);
148
+ };
149
+
150
+ export const section = (icon, text, color = "#22d3ee") =>
151
+ `\n ${gradient(icon + " " + text, color, "#a855f7")} ${hex("─".repeat(Math.max(4, 46 - width(text))), "#4338ca")}`;
152
+
153
+ // Double-line panel with gradient border and centered title
154
+ export const panel = (title, subtitle = "", { minW = 62 } = {}) => {
155
+ const content = rainbow(title) + (subtitle ? c("gray", " " + subtitle) : "");
156
+ const cw = dispLen(content);
157
+ const W = Math.max(cw + 4, minW);
158
+ const total = W - cw;
159
+ const left = " ".repeat(Math.floor(total / 2));
160
+ const right = " ".repeat(Math.ceil(total / 2));
161
+ const border = TRUE_COLOR
162
+ ? gradient("═".repeat(W), "#6366f1", "#a855f7")
163
+ : c("cyan", "═".repeat(W));
164
+ const corner = (ch) => hex(ch, "#818cf8");
165
+ return (
166
+ "\n" +
167
+ corner("╔") + border + corner("╗") + "\n" +
168
+ corner("║") + left + content + right + corner("║") + "\n" +
169
+ corner("╚") + border + corner("╝") + "\n"
170
+ );
171
+ };
172
+
173
+ export const banner = (title, subtitle = "", opts = {}) =>
174
+ panel(title, subtitle, opts);
175
+
176
+ export const badge = (icon, text, color = "brightCyan") =>
177
+ ` ${c(color, icon + " " + text)}`;
178
+
179
+ // Status dot (green by default, red/amber for failures/warnings)
180
+ export const dot = (color = "#34d399") => hex("●", color);
181
+
182
+ // Typewriter effect: prints the text character by character (only on a TTY)
183
+ export const typewriter = async (
184
+ text,
185
+ { ms = 12, color = "#34d399" } = {},
186
+ ) => {
187
+ if (!enabled) {
188
+ console.log(text);
189
+ return;
190
+ }
191
+ const paint = TRUE_COLOR
192
+ ? (() => {
193
+ const [r, g, b] = hex2rgb(color);
194
+ return (ch) => `\x1b[38;2;${r};${g};${b}m${ch}`;
195
+ })()
196
+ : (ch) => `\x1b[96m${ch}`;
197
+ for (const ch of [...text]) {
198
+ process.stdout.write(paint(ch));
199
+ await new Promise((res) => setTimeout(res, ms));
200
+ }
201
+ process.stdout.write("\x1b[0m\n");
202
+ };
203
+
204
+ // Global Ctrl+C handler
205
+ export const setupCtrlC = () => {
206
+ process.on("SIGINT", () => {
207
+ console.log(`\n\n${rainbow("👋 Bye!")}\n`);
208
+ process.exit(0);
209
+ });
210
+ };
211
+
212
+ // Show help message for Ctrl+C
213
+ export const showExitHint = () => {
214
+ console.log(`${c("gray", "Press Ctrl+C to exit")}`);
215
+ };
@@ -0,0 +1,80 @@
1
+ // bin/utils/helpers.js
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { execSync } from "child_process";
5
+ import { c } from "./colors.js";
6
+
7
+ export const createFile = (filePath, content) => {
8
+ try {
9
+ const dir = path.dirname(filePath);
10
+ if (!fs.existsSync(dir)) {
11
+ fs.mkdirSync(dir, { recursive: true });
12
+ }
13
+ fs.writeFileSync(filePath, content, "utf8");
14
+ console.log(`${c("green", "✅")} Created: ${path.basename(filePath)}`);
15
+ } catch (error) {
16
+ console.error(
17
+ `${c("red", "❌")} Failed to create ${filePath}: ${error.message}`,
18
+ );
19
+ }
20
+ };
21
+
22
+ export const copyFile = (source, dest) => {
23
+ try {
24
+ if (fs.existsSync(source)) {
25
+ const destDir = path.dirname(dest);
26
+ if (!fs.existsSync(destDir)) {
27
+ fs.mkdirSync(destDir, { recursive: true });
28
+ }
29
+ fs.copyFileSync(source, dest);
30
+ console.log(`${c("green", "✅")} Copied: ${path.basename(source)}`);
31
+ return true;
32
+ } else {
33
+ console.log(
34
+ `${c("yellow", "⚠️")} Source not found: ${path.basename(source)}`,
35
+ );
36
+ return false;
37
+ }
38
+ } catch (error) {
39
+ console.error(
40
+ `${c("red", "❌")} Failed to copy ${path.basename(source)}: ${error.message}`,
41
+ );
42
+ return false;
43
+ }
44
+ };
45
+
46
+ export const makeExecutable = (filePath) => {
47
+ try {
48
+ if (fs.existsSync(filePath)) {
49
+ fs.chmodSync(filePath, 0o755);
50
+ console.log(`${c("blue", "🔧")} Executable: ${path.basename(filePath)}`);
51
+ }
52
+ } catch (error) {
53
+ if (process.env.DEBUG) {
54
+ console.error(
55
+ `${c("red", "⚠️")} Could not make executable: ${path.basename(filePath)}`,
56
+ );
57
+ }
58
+ }
59
+ };
60
+
61
+ export const execCmd = (cmd) => {
62
+ try {
63
+ execSync(cmd, {
64
+ stdio: "inherit",
65
+ shell: true,
66
+ });
67
+ return true;
68
+ } catch (error) {
69
+ console.error(`${c("red", "❌")} Command failed: ${cmd}`);
70
+ if (process.env.DEBUG) {
71
+ console.error(error.message);
72
+ }
73
+ return false;
74
+ }
75
+ };
76
+
77
+ // Fixed: Now returns actual package path
78
+ export const getPackagePath = () => {
79
+ return path.join(process.cwd(), "node_modules", "flet-box");
80
+ };
@@ -0,0 +1,96 @@
1
+ // bin/utils/spinner.js
2
+ // Dependency-free CLI spinner (keeps flet-box zero-deps, looks like ora).
3
+
4
+ import { execSync } from "child_process";
5
+ import { c } from "./colors.js";
6
+
7
+ const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
8
+ const INTERVAL = 80;
9
+ const RESET = "\x1b[0m";
10
+ const CYAN = "\x1b[96m";
11
+
12
+ const isTTY = () => Boolean(process.stdout && process.stdout.isTTY);
13
+
14
+ class Spinner {
15
+ constructor(text = "") {
16
+ this.text = text;
17
+ this.frame = 0;
18
+ this.timer = null;
19
+ this.running = false;
20
+ }
21
+
22
+ start() {
23
+ if (this.running) return this;
24
+ this.running = true;
25
+ process.stdout.write("\n");
26
+ this.timer = setInterval(() => this.render(), INTERVAL);
27
+ this.render();
28
+ return this;
29
+ }
30
+
31
+ render() {
32
+ const f = FRAMES[this.frame % FRAMES.length];
33
+ process.stdout.write(`\x1b[1A\x1b[K${CYAN}${f}${RESET} ${this.text}\n`);
34
+ this.frame++;
35
+ }
36
+
37
+ stop(msg) {
38
+ if (!this.running) return;
39
+ this.running = false;
40
+ clearInterval(this.timer);
41
+ process.stdout.write(`\x1b[1A\x1b[K${msg}\n`);
42
+ }
43
+
44
+ succeed(text) {
45
+ this.stop(`${c("green", "✔")} ${text ?? this.text}`);
46
+ }
47
+
48
+ fail(text) {
49
+ this.stop(`${c("red", "✖")} ${text ?? this.text}`);
50
+ }
51
+
52
+ setText(text) {
53
+ this.text = text;
54
+ }
55
+ }
56
+
57
+ // Return a no-op spinner when there is no TTY (instant clean output)
58
+ export const spinner = (text) => {
59
+ if (!isTTY()) {
60
+ console.log(c("gray", text));
61
+ return { succeed: () => {}, fail: () => {}, stop: () => {}, setText: () => {} };
62
+ }
63
+ return new Spinner(text).start();
64
+ };
65
+
66
+ // Run a shell command with a spinner. Silences stdout/stderr; on failure the
67
+ // last lines of stderr are shown.
68
+ export const execSpin = (text, cmd) => {
69
+ if (!isTTY()) {
70
+ try {
71
+ execSync(cmd, { stdio: "inherit", shell: true });
72
+ console.log(c("green", `✔ ${text}`));
73
+ return true;
74
+ } catch {
75
+ console.log(c("red", `✖ ${text}`));
76
+ return false;
77
+ }
78
+ }
79
+ const s = new Spinner(text).start();
80
+ try {
81
+ execSync(cmd, {
82
+ stdio: ["ignore", "pipe", "pipe"],
83
+ shell: true,
84
+ encoding: "utf8",
85
+ });
86
+ s.succeed(text);
87
+ return true;
88
+ } catch (e) {
89
+ s.fail(text);
90
+ const err = (e.stderr || "").toString().trim();
91
+ if (err) {
92
+ console.log(c("red", err.split("\n").slice(-4).join("\n")));
93
+ }
94
+ return false;
95
+ }
96
+ };