blumenjs 0.1.4 → 0.1.5

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.
@@ -0,0 +1,346 @@
1
+ // cli/commands/deploy.ts
2
+ import { execSync, spawnSync } from "child_process";
3
+ import * as fs2 from "fs";
4
+ import * as path2 from "path";
5
+
6
+ // cli/utils.ts
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { fileURLToPath } from "url";
10
+ var c = {
11
+ reset: "\x1B[0m",
12
+ bold: "\x1B[1m",
13
+ dim: "\x1B[2m",
14
+ red: "\x1B[31m",
15
+ green: "\x1B[32m",
16
+ yellow: "\x1B[33m",
17
+ blue: "\x1B[34m",
18
+ magenta: "\x1B[35m",
19
+ cyan: "\x1B[36m",
20
+ white: "\x1B[37m",
21
+ gray: "\x1B[90m"
22
+ };
23
+ var log = {
24
+ info: (msg) => console.log(` ${c.magenta}\u25CF${c.reset} ${msg}`),
25
+ success: (msg) => console.log(` ${c.green}\u2713${c.reset} ${msg}`),
26
+ error: (msg) => console.error(` ${c.red}\u2717${c.reset} ${msg}`),
27
+ warn: (msg) => console.log(` ${c.yellow}\u26A0${c.reset} ${msg}`),
28
+ step: (msg) => console.log(` ${c.dim}\u2192${c.reset} ${msg}`),
29
+ blank: () => console.log("")
30
+ };
31
+ function getVersion() {
32
+ try {
33
+ const thisFile = fileURLToPath(import.meta.url);
34
+ let dir = path.dirname(thisFile);
35
+ for (let i = 0; i < 5; i++) {
36
+ const pkgFile = path.join(dir, "package.json");
37
+ if (fs.existsSync(pkgFile)) {
38
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf-8"));
39
+ if (pkg.name === "blumenjs" || pkg.name === "go-react-ssr") {
40
+ return pkg.version;
41
+ }
42
+ }
43
+ dir = path.dirname(dir);
44
+ }
45
+ return "0.0.0";
46
+ } catch {
47
+ return "0.0.0";
48
+ }
49
+ }
50
+ function banner() {
51
+ const version = getVersion();
52
+ console.log("");
53
+ console.log(
54
+ ` ${c.magenta}${c.bold}\u{1F338} Blumen${c.reset} ${c.dim}v${version}${c.reset}`
55
+ );
56
+ console.log(
57
+ ` ${c.dim}The React framework powered by Go${c.reset}`
58
+ );
59
+ console.log("");
60
+ }
61
+ function divider() {
62
+ console.log(` ${c.dim}${"\u2500".repeat(48)}${c.reset}`);
63
+ }
64
+ async function confirm(question, defaultYes = true) {
65
+ const readline = await import("readline");
66
+ const rl = readline.createInterface({
67
+ input: process.stdin,
68
+ output: process.stdout
69
+ });
70
+ const hint = defaultYes ? "Y/n" : "y/N";
71
+ return new Promise((resolve) => {
72
+ rl.question(` ${c.bold}${question}${c.reset} ${c.dim}(${hint})${c.reset} `, (answer) => {
73
+ rl.close();
74
+ const a = answer.trim().toLowerCase();
75
+ if (a === "")
76
+ resolve(defaultYes);
77
+ else
78
+ resolve(a === "y" || a === "yes");
79
+ });
80
+ });
81
+ }
82
+
83
+ // cli/commands/deploy.ts
84
+ var PLATFORMS = [
85
+ { name: "Docker (any cloud)", ok: true, note: "Best option \u2014 multi-stage Dockerfile included" },
86
+ { name: "Railway", ok: true, note: "Auto-detects Dockerfile, deploy with railway up" },
87
+ { name: "Fly.io", ok: true, note: "Docker-based deploys via fly deploy" },
88
+ { name: "Render", ok: true, note: "Docker support, auto-deploy from Git" },
89
+ { name: "AWS EC2 / GCP / Azure", ok: true, note: "Full control, any runtime" },
90
+ { name: "DigitalOcean", ok: true, note: "App Platform supports Docker" },
91
+ { name: "Vercel", ok: false, note: "Node-only, no Go runtime" },
92
+ { name: "Netlify", ok: false, note: "Static/serverless only, no Go" }
93
+ ];
94
+ function hasCommand(cmd) {
95
+ try {
96
+ execSync(`which ${cmd}`, { stdio: "pipe" });
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ function ensureDockerfile() {
103
+ if (!fs2.existsSync("Dockerfile")) {
104
+ log.error("No Dockerfile found in the current directory.");
105
+ log.info(
106
+ `Run ${c.bold}blumen create${c.reset} to scaffold a project with Docker support.`
107
+ );
108
+ process.exit(1);
109
+ }
110
+ }
111
+ async function deployDocker() {
112
+ log.info("Docker Deployment\n");
113
+ ensureDockerfile();
114
+ if (!hasCommand("docker")) {
115
+ log.error("Docker is not installed.");
116
+ log.info(
117
+ `Install Docker: ${c.cyan}https://docs.docker.com/get-docker/${c.reset}`
118
+ );
119
+ process.exit(1);
120
+ }
121
+ const projectName = path2.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
122
+ const imageName = `blumen-${projectName}`;
123
+ log.step(`Building image: ${c.bold}${imageName}${c.reset}...`);
124
+ divider();
125
+ log.blank();
126
+ const buildResult = spawnSync("docker", ["build", "-t", imageName, "."], {
127
+ cwd: process.cwd(),
128
+ stdio: "inherit"
129
+ });
130
+ log.blank();
131
+ divider();
132
+ if (buildResult.status !== 0) {
133
+ log.error("Docker build failed.");
134
+ process.exit(1);
135
+ }
136
+ log.success(`Image ${c.bold}${imageName}${c.reset} built successfully!`);
137
+ log.blank();
138
+ const shouldRun = await confirm("Run the container now?");
139
+ if (shouldRun) {
140
+ log.step(`Starting ${c.bold}${imageName}${c.reset} on port 3000...`);
141
+ log.blank();
142
+ console.log(
143
+ ` ${c.dim}\u279C${c.reset} ${c.bold}App${c.reset}: ${c.cyan}http://localhost:3000${c.reset}`
144
+ );
145
+ console.log(
146
+ ` ${c.dim}Press ${c.bold}Ctrl+C${c.reset}${c.dim} to stop.${c.reset}`
147
+ );
148
+ log.blank();
149
+ spawnSync("docker", ["run", "--rm", "-p", "3000:3000", imageName], {
150
+ cwd: process.cwd(),
151
+ stdio: "inherit"
152
+ });
153
+ } else {
154
+ log.blank();
155
+ console.log(` ${c.dim}Run manually:${c.reset}`);
156
+ console.log(` docker run -p 3000:3000 ${imageName}`);
157
+ log.blank();
158
+ }
159
+ }
160
+ async function deployFly() {
161
+ log.info("Fly.io Deployment\n");
162
+ ensureDockerfile();
163
+ const projectName = path2.basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
164
+ if (!fs2.existsSync("fly.toml")) {
165
+ log.step("Generating fly.toml...");
166
+ const flyConfig = `# Fly.io configuration for Blumen app
167
+ # Deploy with: fly deploy
168
+
169
+ app = "${projectName}"
170
+ primary_region = "iad"
171
+
172
+ [build]
173
+
174
+ [http_service]
175
+ internal_port = 3000
176
+ force_https = true
177
+ auto_stop_machines = "stop"
178
+ auto_start_machines = true
179
+ min_machines_running = 0
180
+
181
+ [checks]
182
+ [checks.health]
183
+ type = "http"
184
+ port = 3000
185
+ path = "/"
186
+ interval = "30s"
187
+ timeout = "5s"
188
+
189
+ [[vm]]
190
+ memory = "512mb"
191
+ cpu_kind = "shared"
192
+ cpus = 1
193
+ `;
194
+ fs2.writeFileSync("fly.toml", flyConfig);
195
+ log.success("fly.toml created");
196
+ } else {
197
+ log.info("fly.toml already exists, skipping generation.");
198
+ }
199
+ log.blank();
200
+ if (!hasCommand("fly") && !hasCommand("flyctl")) {
201
+ log.warn("Fly CLI is not installed.");
202
+ log.blank();
203
+ console.log(` ${c.dim}Install:${c.reset}`);
204
+ console.log(` curl -L https://fly.io/install.sh | sh`);
205
+ log.blank();
206
+ console.log(` ${c.dim}Then deploy:${c.reset}`);
207
+ console.log(` fly launch`);
208
+ console.log(` fly deploy`);
209
+ log.blank();
210
+ return;
211
+ }
212
+ const shouldDeploy = await confirm("Deploy to Fly.io now?");
213
+ if (shouldDeploy) {
214
+ log.step("Deploying to Fly.io...");
215
+ divider();
216
+ log.blank();
217
+ const flyCmd = hasCommand("fly") ? "fly" : "flyctl";
218
+ spawnSync(flyCmd, ["deploy"], {
219
+ cwd: process.cwd(),
220
+ stdio: "inherit"
221
+ });
222
+ log.blank();
223
+ divider();
224
+ log.success("Deployment complete!");
225
+ } else {
226
+ log.blank();
227
+ console.log(` ${c.dim}Deploy later:${c.reset}`);
228
+ console.log(` fly deploy`);
229
+ log.blank();
230
+ }
231
+ }
232
+ async function deployRailway() {
233
+ log.info("Railway Deployment\n");
234
+ ensureDockerfile();
235
+ if (!fs2.existsSync("railway.toml")) {
236
+ log.step("Generating railway.toml...");
237
+ const railwayConfig = `# Railway configuration for Blumen app
238
+ # Deploy with: railway up
239
+
240
+ [build]
241
+ builder = "DOCKERFILE"
242
+ dockerfilePath = "Dockerfile"
243
+
244
+ [deploy]
245
+ startCommand = ""
246
+ healthcheckPath = "/"
247
+ healthcheckTimeout = 30
248
+ restartPolicyType = "ON_FAILURE"
249
+ restartPolicyMaxRetries = 5
250
+ `;
251
+ fs2.writeFileSync("railway.toml", railwayConfig);
252
+ log.success("railway.toml created");
253
+ } else {
254
+ log.info("railway.toml already exists, skipping generation.");
255
+ }
256
+ log.blank();
257
+ if (!hasCommand("railway")) {
258
+ log.warn("Railway CLI is not installed.");
259
+ log.blank();
260
+ console.log(` ${c.dim}Install:${c.reset}`);
261
+ console.log(` npm install -g @railway/cli`);
262
+ log.blank();
263
+ console.log(` ${c.dim}Then deploy:${c.reset}`);
264
+ console.log(` railway login`);
265
+ console.log(` railway up`);
266
+ log.blank();
267
+ return;
268
+ }
269
+ const shouldDeploy = await confirm("Deploy to Railway now?");
270
+ if (shouldDeploy) {
271
+ log.step("Deploying to Railway...");
272
+ divider();
273
+ log.blank();
274
+ spawnSync("railway", ["up"], {
275
+ cwd: process.cwd(),
276
+ stdio: "inherit"
277
+ });
278
+ log.blank();
279
+ divider();
280
+ log.success("Deployment complete!");
281
+ } else {
282
+ log.blank();
283
+ console.log(` ${c.dim}Deploy later:${c.reset}`);
284
+ console.log(` railway up`);
285
+ log.blank();
286
+ }
287
+ }
288
+ function showInfo() {
289
+ log.info("Hosting Compatibility\n");
290
+ console.log(` Blumen apps require ${c.bold}Go${c.reset} + ${c.bold}Node.js${c.reset} \u2014 use Docker-compatible platforms.
291
+ `);
292
+ const maxName = Math.max(...PLATFORMS.map((p) => p.name.length));
293
+ for (const p of PLATFORMS) {
294
+ const icon = p.ok ? `${c.green}\u2713${c.reset}` : `${c.red}\u2717${c.reset}`;
295
+ const name = p.name.padEnd(maxName + 2);
296
+ const note = `${c.dim}${p.note}${c.reset}`;
297
+ console.log(` ${icon} ${name} ${note}`);
298
+ }
299
+ log.blank();
300
+ divider();
301
+ log.blank();
302
+ console.log(` ${c.bold}Quick start:${c.reset}`);
303
+ log.blank();
304
+ console.log(` ${c.dim}Docker (local):${c.reset} docker compose up`);
305
+ console.log(` ${c.dim}Fly.io:${c.reset} blumen deploy fly`);
306
+ console.log(` ${c.dim}Railway:${c.reset} blumen deploy railway`);
307
+ log.blank();
308
+ }
309
+ async function deploy(subcommand) {
310
+ banner();
311
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
312
+ log.info("Deploy your Blumen app\n");
313
+ console.log(` ${c.bold}Usage${c.reset} blumen deploy ${c.dim}<target>${c.reset}
314
+ `);
315
+ console.log(` ${c.bold}Targets${c.reset}`);
316
+ console.log(` docker Build Docker image and optionally run`);
317
+ console.log(` fly Generate config and deploy to Fly.io`);
318
+ console.log(` railway Generate config and deploy to Railway`);
319
+ console.log(` info Show hosting compatibility matrix`);
320
+ console.log("");
321
+ return;
322
+ }
323
+ switch (subcommand) {
324
+ case "docker":
325
+ await deployDocker();
326
+ break;
327
+ case "fly":
328
+ await deployFly();
329
+ break;
330
+ case "railway":
331
+ await deployRailway();
332
+ break;
333
+ case "info":
334
+ showInfo();
335
+ break;
336
+ default:
337
+ log.error(`Unknown deploy target: ${c.bold}${subcommand}${c.reset}`);
338
+ log.info(
339
+ `Run ${c.bold}blumen deploy --help${c.reset} for available targets.`
340
+ );
341
+ process.exit(1);
342
+ }
343
+ }
344
+ export {
345
+ deploy
346
+ };