@tothalex/nulljs 0.0.54 → 0.0.57

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 (2) hide show
  1. package/dist/cli.js +2553 -0
  2. package/package.json +2 -2
package/dist/cli.js ADDED
@@ -0,0 +1,2553 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
13
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
+ var __require = import.meta.require;
15
+ // ../../packages/api/types/index.ts
16
+ var init_types = () => {};
17
+
18
+ // ../../packages/api/actions/client.ts
19
+ var buildUrl = (path, options) => {
20
+ let fullPath = options?.url ? `${options.url}${path}` : path;
21
+ if (options?.query) {
22
+ const params = [];
23
+ for (const [key, value] of Object.entries(options.query)) {
24
+ if (value === undefined || value === null)
25
+ continue;
26
+ if (Array.isArray(value)) {
27
+ params.push(`${key}=${value.join(",")}`);
28
+ } else {
29
+ params.push(`${key}=${encodeURIComponent(String(value))}`);
30
+ }
31
+ }
32
+ const queryString = params.join("&");
33
+ if (queryString) {
34
+ fullPath = `${fullPath}?${queryString}`;
35
+ }
36
+ }
37
+ return fullPath;
38
+ }, apiGet = async (path, options) => {
39
+ const url = options?.url ? `${options.url}${path}` : path;
40
+ const headers = {};
41
+ if (options?.publicKey) {
42
+ headers["x-public-key"] = options.publicKey;
43
+ }
44
+ const response = await fetch(url, { headers });
45
+ if (!response.ok) {
46
+ const message = await response.text().catch(() => "");
47
+ throw new Error(`HTTP error! status: ${response.status}${message ? ` message: ${message}` : ""}`);
48
+ }
49
+ return response.json();
50
+ };
51
+
52
+ // ../../packages/api/actions/deployments.ts
53
+ var init_deployments = () => {};
54
+
55
+ // ../../packages/api/actions/deploy.ts
56
+ var getMimeType = (fileName) => {
57
+ if (fileName.endsWith(".js"))
58
+ return "application/javascript";
59
+ if (fileName.endsWith(".css"))
60
+ return "text/css";
61
+ if (fileName.endsWith(".html"))
62
+ return "text/html";
63
+ if (fileName.endsWith(".json"))
64
+ return "application/json";
65
+ return "application/octet-stream";
66
+ }, createFormDataWithInfo = (assets2) => {
67
+ const form = new FormData;
68
+ const blobs = [];
69
+ for (const asset of assets2) {
70
+ const mime = getMimeType(asset.fileName);
71
+ const buffer = Buffer.from(asset.code);
72
+ const blob = new Blob([buffer], { type: mime });
73
+ form.append(asset.fileName, blob);
74
+ blobs.push({ fileName: asset.fileName, size: blob.size, type: mime });
75
+ }
76
+ return { form, blobs };
77
+ }, createSignature = async (blobs, privateKey) => {
78
+ const sortedBlobs = [...blobs].sort((a, b) => a.fileName.localeCompare(b.fileName));
79
+ const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
80
+ const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
81
+ return btoa(String.fromCharCode(...new Uint8Array(sign)));
82
+ }, createFunctionDeployment = async (deployment2, options) => {
83
+ const handler = deployment2.assets.find((asset) => asset.fileName === "handler.js");
84
+ if (!handler) {
85
+ throw new Error(`Handler not found for ${deployment2.name}`);
86
+ }
87
+ const { form, blobs } = createFormDataWithInfo([handler]);
88
+ const signature = await createSignature(blobs, options.privateKey);
89
+ const baseUrl = options.url || "";
90
+ const response = await fetch(`${baseUrl}/api/deployment`, {
91
+ method: "POST",
92
+ headers: {
93
+ authorization: signature
94
+ },
95
+ body: form
96
+ });
97
+ if (!response.ok) {
98
+ const errorText = await response.text();
99
+ throw new Error(`Deployment failed (${response.status}): ${errorText}`);
100
+ }
101
+ }, createReactDeployment = async (deployment2, options) => {
102
+ const { form, blobs } = createFormDataWithInfo(deployment2.assets);
103
+ const signature = await createSignature(blobs, options.privateKey);
104
+ const baseUrl = options.url || "";
105
+ const response = await fetch(`${baseUrl}/api/deployment/react`, {
106
+ method: "POST",
107
+ headers: {
108
+ authorization: signature
109
+ },
110
+ body: form
111
+ });
112
+ if (!response.ok) {
113
+ const errorText = await response.text();
114
+ throw new Error(`Deployment failed (${response.status}): ${errorText}`);
115
+ }
116
+ };
117
+
118
+ // ../../packages/api/actions/invocations.ts
119
+ var init_invocations = () => {};
120
+
121
+ // ../../packages/api/actions/logs.ts
122
+ var getSystemLogs = async (public_key, query, url) => {
123
+ const path = buildUrl("/api/system/logs", { url, query });
124
+ return apiGet(path, { publicKey: public_key });
125
+ }, searchDeploymentLogs = async (public_key, props, url) => {
126
+ const path = buildUrl("/api/logs/search", { url, query: props });
127
+ return apiGet(path, { publicKey: public_key });
128
+ };
129
+ var init_logs = () => {};
130
+
131
+ // ../../packages/api/actions/assets.ts
132
+ var init_assets = () => {};
133
+
134
+ // ../../packages/api/actions/activity.ts
135
+ var init_activity = () => {};
136
+
137
+ // ../../packages/api/actions/secrets.ts
138
+ var createSignatureHeader = async (privateKey, props) => {
139
+ const timestamp = new Date().toISOString();
140
+ const raw = `${props.method}-${props.path}-${timestamp}${props.body ? "-" + props.body : ""}`;
141
+ const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(raw));
142
+ const signature = btoa(String.fromCharCode(...new Uint8Array(sign)));
143
+ const header = {
144
+ authorization: signature,
145
+ "x-time": timestamp
146
+ };
147
+ if (props.body) {
148
+ header["x-body"] = btoa(props.body);
149
+ }
150
+ return header;
151
+ }, listSecrets = async (options) => {
152
+ const baseUrl = options.url || "";
153
+ const path = `${baseUrl}/api/secrets`;
154
+ const headers = await createSignatureHeader(options.privateKey, {
155
+ method: "GET",
156
+ path
157
+ });
158
+ let response;
159
+ try {
160
+ response = await fetch(path, {
161
+ method: "GET",
162
+ headers
163
+ });
164
+ } catch (err) {
165
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
166
+ }
167
+ if (!response.ok) {
168
+ const errorText = await response.text();
169
+ throw new Error(`Failed to list secrets (${response.status}): ${errorText}`);
170
+ }
171
+ return response.json();
172
+ }, createSecret = async (secret, options) => {
173
+ const baseUrl = options.url || "";
174
+ const path = `${baseUrl}/api/secrets`;
175
+ const body = JSON.stringify(secret);
176
+ const headers = await createSignatureHeader(options.privateKey, {
177
+ method: "POST",
178
+ path,
179
+ body
180
+ });
181
+ const response = await fetch(path, {
182
+ method: "POST",
183
+ headers: {
184
+ "Content-Type": "application/json",
185
+ ...headers
186
+ },
187
+ body
188
+ });
189
+ if (!response.ok) {
190
+ const errorText = await response.text();
191
+ throw new Error(`Failed to create secret (${response.status}): ${errorText}`);
192
+ }
193
+ };
194
+
195
+ // ../../packages/api/actions/index.ts
196
+ var init_actions = __esm(() => {
197
+ init_deployments();
198
+ init_invocations();
199
+ init_logs();
200
+ init_assets();
201
+ init_activity();
202
+ });
203
+
204
+ // ../../packages/api/index.ts
205
+ var init_api = __esm(() => {
206
+ init_types();
207
+ init_actions();
208
+ });
209
+
210
+ // src/config/index.ts
211
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
212
+ import { join } from "path";
213
+ import chalk from "chalk";
214
+ var getLocalConfigDir = () => join(process.cwd(), ".nulljs"), getLocalConfigFile = () => join(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
215
+ const localDir = getLocalConfigDir();
216
+ if (!existsSync(localDir)) {
217
+ mkdirSync(localDir, { recursive: true });
218
+ }
219
+ }, readConfigStore = () => {
220
+ try {
221
+ const localFile = getLocalConfigFile();
222
+ if (!existsSync(localFile)) {
223
+ return null;
224
+ }
225
+ const data = readFileSync(localFile, "utf-8");
226
+ return JSON.parse(data);
227
+ } catch {
228
+ return null;
229
+ }
230
+ }, writeConfigStore = (store) => {
231
+ ensureLocalConfigDir();
232
+ writeFileSync(getLocalConfigFile(), JSON.stringify(store, null, 2));
233
+ }, readLocalConfig = () => {
234
+ const store = readConfigStore();
235
+ if (!store)
236
+ return null;
237
+ return store.configs[store.current] ?? null;
238
+ }, getConfig = (name) => {
239
+ const store = readConfigStore();
240
+ if (!store)
241
+ return null;
242
+ return store.configs[name] ?? null;
243
+ }, listConfigs = () => {
244
+ const store = readConfigStore();
245
+ if (!store)
246
+ return null;
247
+ return {
248
+ configs: Object.values(store.configs),
249
+ current: store.current
250
+ };
251
+ }, useConfig = (name) => {
252
+ const store = readConfigStore();
253
+ if (!store) {
254
+ console.error(chalk.red('\u2717 No configurations found. Run "nulljs dev" first.'));
255
+ return false;
256
+ }
257
+ if (!store.configs[name]) {
258
+ console.error(chalk.red(`\u2717 Config "${name}" not found.`));
259
+ console.log(chalk.gray("Available configs:"));
260
+ Object.keys(store.configs).forEach((n) => {
261
+ console.log(chalk.gray(` - ${n}`));
262
+ });
263
+ return false;
264
+ }
265
+ store.current = name;
266
+ writeConfigStore(store);
267
+ console.log(chalk.green(`\u2713 Now using config "${name}"`));
268
+ return true;
269
+ }, createConfig = async (name, api) => {
270
+ const store = readConfigStore() ?? { current: "dev", configs: {} };
271
+ if (store.configs[name]) {
272
+ console.error(chalk.red(`\u2717 Config "${name}" already exists.`));
273
+ process.exit(1);
274
+ }
275
+ const keyPair = await crypto.subtle.generateKey({
276
+ name: "Ed25519",
277
+ namedCurve: "Ed25519"
278
+ }, true, ["sign", "verify"]);
279
+ const privateKeyBuffer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
280
+ const publicKeyBuffer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
281
+ const privateKey = btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer)));
282
+ const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)));
283
+ const newConfig = {
284
+ name,
285
+ key: {
286
+ public: publicKey,
287
+ private: privateKey
288
+ },
289
+ api
290
+ };
291
+ store.configs[name] = newConfig;
292
+ writeConfigStore(store);
293
+ console.log(chalk.green(`\u2713 Config "${name}" created`));
294
+ console.log(chalk.blue(" API:") + ` ${api}`);
295
+ console.log(chalk.blue(" Public Key:") + ` ${publicKey.substring(0, 30)}...`);
296
+ return newConfig;
297
+ }, getOrCreateLocalDevConfig = async (api = "http://localhost:3000") => {
298
+ const store = readConfigStore();
299
+ if (store?.configs["dev"]) {
300
+ return store.configs["dev"];
301
+ }
302
+ const keyPair = await crypto.subtle.generateKey({
303
+ name: "Ed25519",
304
+ namedCurve: "Ed25519"
305
+ }, true, ["sign", "verify"]);
306
+ const privateKeyBuffer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
307
+ const publicKeyBuffer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
308
+ const privateKey = btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer)));
309
+ const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)));
310
+ const devConfig = {
311
+ name: "dev",
312
+ key: {
313
+ public: publicKey,
314
+ private: privateKey
315
+ },
316
+ api
317
+ };
318
+ const newStore = store ?? { current: "dev", configs: {} };
319
+ newStore.configs["dev"] = devConfig;
320
+ newStore.current = "dev";
321
+ writeConfigStore(newStore);
322
+ return devConfig;
323
+ }, loadPrivateKey = async (config) => {
324
+ const privateKeyBytes = Uint8Array.from(atob(config.key.private), (c) => c.charCodeAt(0));
325
+ return crypto.subtle.importKey("pkcs8", privateKeyBytes, { name: "Ed25519", namedCurve: "Ed25519" }, false, ["sign"]);
326
+ };
327
+ var init_config = () => {};
328
+
329
+ // src/lib/server.ts
330
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "fs";
331
+ import { join as join2, dirname } from "path";
332
+ import chalk2 from "chalk";
333
+ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
334
+ return `${process.platform}-${process.arch}`;
335
+ }, findProjectRoot = (startPath = process.cwd()) => {
336
+ let currentPath = startPath;
337
+ while (currentPath !== dirname(currentPath)) {
338
+ if (existsSync2(join2(currentPath, "package.json"))) {
339
+ return currentPath;
340
+ }
341
+ currentPath = dirname(currentPath);
342
+ }
343
+ return startPath;
344
+ }, findMonorepoRoot = (startPath = process.cwd()) => {
345
+ let currentPath = startPath;
346
+ while (currentPath !== dirname(currentPath)) {
347
+ if (existsSync2(join2(currentPath, "Cargo.toml")) && existsSync2(join2(currentPath, "package.json"))) {
348
+ return currentPath;
349
+ }
350
+ currentPath = dirname(currentPath);
351
+ }
352
+ return null;
353
+ }, getLocalBinaryPath = () => {
354
+ const envBinary = process.env.NULLJS_SERVER_BINARY;
355
+ if (envBinary && existsSync2(envBinary)) {
356
+ return { path: envBinary, source: "local-env" };
357
+ }
358
+ const monorepoRoot = findMonorepoRoot() || findMonorepoRoot(CLI_DIR);
359
+ if (monorepoRoot) {
360
+ const debugBinary = join2(monorepoRoot, "target", "debug", "server");
361
+ if (existsSync2(debugBinary)) {
362
+ return { path: debugBinary, source: "local-debug" };
363
+ }
364
+ const releaseBinary = join2(monorepoRoot, "target", "release", "server");
365
+ if (existsSync2(releaseBinary)) {
366
+ return { path: releaseBinary, source: "local-release" };
367
+ }
368
+ }
369
+ return null;
370
+ }, waitForServer = async (port, maxRetries = 30, delayMs = 1000) => {
371
+ for (let i = 0;i < maxRetries; i++) {
372
+ try {
373
+ const response = await fetch(`http://localhost:${port}/api/health`);
374
+ if (response.ok) {
375
+ return;
376
+ }
377
+ } catch {}
378
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
379
+ }
380
+ throw new Error(`Server did not start within ${maxRetries * delayMs / 1000}s`);
381
+ }, buildDevArgs = async () => {
382
+ const devConfig = await getOrCreateLocalDevConfig();
383
+ const args = ["--dev"];
384
+ args.push("--api-port", "3000");
385
+ args.push("--gateway-port", "3001");
386
+ const projectRoot = findProjectRoot();
387
+ const cloudPath = join2(projectRoot, ".nulljs");
388
+ args.push("--cloud-path", cloudPath);
389
+ if (!existsSync2(cloudPath)) {
390
+ mkdirSync2(cloudPath, { recursive: true });
391
+ }
392
+ args.push("--public-key", devConfig.key.public);
393
+ return args;
394
+ }, startServer = async () => {
395
+ const localBinary = getLocalBinaryPath();
396
+ let binaryPath;
397
+ let binarySource;
398
+ if (localBinary) {
399
+ console.log(chalk2.cyan(`Using local binary: ${localBinary.path}`));
400
+ binaryPath = localBinary.path;
401
+ binarySource = localBinary.source;
402
+ } else {
403
+ const platformKey = getPlatformKey();
404
+ const pkgName = PLATFORMS[platformKey];
405
+ if (!pkgName) {
406
+ console.error(chalk2.red(`\u2717 Unsupported platform: ${platformKey}`));
407
+ throw new Error(`Unsupported platform: ${platformKey}`);
408
+ }
409
+ binaryPath = __require.resolve(`${pkgName}/bin/server`);
410
+ binarySource = "npm";
411
+ console.log(chalk2.cyan(`Using npm binary: ${pkgName}`));
412
+ }
413
+ try {
414
+ const args = await buildDevArgs();
415
+ console.log(chalk2.yellow(`Starting server with args: ${args.join(" ")}`));
416
+ const proc = Bun.spawn([binaryPath, ...args], {
417
+ stdout: "ignore",
418
+ stderr: "ignore"
419
+ });
420
+ console.log(chalk2.green("\u2713 Server process started, waiting for server to be ready..."));
421
+ await waitForServer(3000);
422
+ console.log(chalk2.green("\u2713 Server is ready"));
423
+ return { process: proc, binarySource, binaryPath };
424
+ } catch (error) {
425
+ console.error(chalk2.red(`\u2717 Failed to start server: ${error instanceof Error ? error.message : String(error)}`));
426
+ throw error;
427
+ }
428
+ };
429
+ var init_server = __esm(() => {
430
+ init_config();
431
+ CLI_DIR = dirname(realpathSync(import.meta.dir));
432
+ PLATFORMS = {
433
+ "linux-x64": "@tothalex/nulljs-linux-x64",
434
+ "linux-arm64": "@tothalex/nulljs-linux-arm64",
435
+ "darwin-arm64": "@tothalex/nulljs-darwin-arm64"
436
+ };
437
+ });
438
+
439
+ // src/lib/bundle/react.ts
440
+ import { basename, extname } from "path";
441
+ import react from "@vitejs/plugin-react";
442
+ import tailwindcss from "@tailwindcss/vite";
443
+ var jsConfig = ({ filePath }) => {
444
+ const entry = basename(filePath, extname(filePath));
445
+ const virtualPrefix = `virtual:config/${entry}.tsx`;
446
+ return {
447
+ name: "nulljs-config-plugin",
448
+ apply: "build",
449
+ config: async (config, { command }) => {
450
+ if (command !== "build") {
451
+ return config;
452
+ }
453
+ return {
454
+ build: {
455
+ ssr: true,
456
+ rollupOptions: {
457
+ input: {
458
+ [entry]: virtualPrefix
459
+ },
460
+ external: ["react", "react/jsx-runtime"],
461
+ output: {
462
+ entryFileNames: "[name].js"
463
+ },
464
+ preserveEntrySignatures: "strict"
465
+ }
466
+ }
467
+ };
468
+ },
469
+ resolveId: (id) => {
470
+ if (id === virtualPrefix) {
471
+ return id;
472
+ }
473
+ return null;
474
+ },
475
+ load: (id) => {
476
+ if (id === virtualPrefix) {
477
+ const script = `
478
+ import { config } from "${filePath.replace(/\\/g, "\\\\")}";
479
+
480
+ export default config;
481
+ `;
482
+ return script;
483
+ }
484
+ return null;
485
+ },
486
+ transform: (code, id) => {
487
+ if (id === filePath) {
488
+ const configMatch = code.match(/export const config = \{[^}]*\};/);
489
+ return configMatch ? { code: configMatch[0], map: null } : null;
490
+ }
491
+ }
492
+ };
493
+ }, spaConfigConfig = (filePath) => {
494
+ return {
495
+ logLevel: "error",
496
+ plugins: [
497
+ jsConfig({
498
+ filePath
499
+ })
500
+ ],
501
+ build: {
502
+ outDir: "/tmp"
503
+ }
504
+ };
505
+ }, jsSpa = ({ filePath }) => {
506
+ const entry = basename(filePath, extname(filePath));
507
+ const virtualPrefix = `virtual:spa/${entry}.tsx`;
508
+ return {
509
+ name: "nulljs-spa-client-plugin",
510
+ apply: "build",
511
+ config: async (config, { command }) => {
512
+ if (command !== "build") {
513
+ return config;
514
+ }
515
+ return {
516
+ build: {
517
+ rollupOptions: {
518
+ input: {
519
+ [entry]: virtualPrefix
520
+ },
521
+ external: ["cloud"],
522
+ output: {
523
+ entryFileNames: "[name].js"
524
+ }
525
+ }
526
+ },
527
+ plugins: [react()]
528
+ };
529
+ },
530
+ resolveId: (id) => {
531
+ if (id === virtualPrefix) {
532
+ return id;
533
+ }
534
+ return null;
535
+ },
536
+ load: (id) => {
537
+ if (id === virtualPrefix) {
538
+ const script = `
539
+ import { StrictMode } from 'react'
540
+ import { createRoot } from 'react-dom/client'
541
+
542
+ import { Page } from "${filePath.replace(/\\/g, "\\\\")}";
543
+
544
+ createRoot(document.getElementById('root')!).render(
545
+ <StrictMode>
546
+ <Page />
547
+ </StrictMode>
548
+ )`;
549
+ return script;
550
+ }
551
+ return null;
552
+ }
553
+ };
554
+ }, spaClientConfig = (filePath) => {
555
+ return {
556
+ logLevel: "error",
557
+ plugins: [
558
+ jsSpa({
559
+ filePath
560
+ }),
561
+ tailwindcss()
562
+ ],
563
+ build: {
564
+ outDir: "/tmp"
565
+ }
566
+ };
567
+ };
568
+ var init_react = () => {};
569
+
570
+ // src/lib/bundle/external.ts
571
+ var external;
572
+ var init_external = __esm(() => {
573
+ external = [
574
+ "cloud/cache",
575
+ "cloud/event",
576
+ "cloud",
577
+ "cloud/postgres",
578
+ "cloud/secret",
579
+ "cloud/uuid",
580
+ "assert",
581
+ "buffer",
582
+ "crypto",
583
+ "dns",
584
+ "events",
585
+ "net",
586
+ "os",
587
+ "process",
588
+ "stream/web",
589
+ "string_decoder",
590
+ "timers",
591
+ "tty",
592
+ "url",
593
+ "util",
594
+ "zlib"
595
+ ];
596
+ });
597
+
598
+ // src/lib/bundle/function.ts
599
+ var jsFunction = ({ filePath }) => {
600
+ return {
601
+ name: "nulljs-function-plugin",
602
+ apply: "build",
603
+ config: async (config, { command }) => {
604
+ if (command !== "build") {
605
+ return config;
606
+ }
607
+ return {
608
+ build: {
609
+ rollupOptions: {
610
+ input: {
611
+ handler: filePath
612
+ },
613
+ external,
614
+ output: {
615
+ entryFileNames: "[name].js"
616
+ },
617
+ preserveEntrySignatures: "strict"
618
+ }
619
+ }
620
+ };
621
+ }
622
+ };
623
+ }, functionConfig = (filePath) => {
624
+ return {
625
+ logLevel: "error",
626
+ plugins: [
627
+ jsFunction({
628
+ filePath
629
+ })
630
+ ],
631
+ build: {
632
+ outDir: "/tmp",
633
+ minify: false
634
+ }
635
+ };
636
+ }, jsFunctionWatch = (entries, callbacks) => {
637
+ const outputToEntry = new Map;
638
+ return {
639
+ name: "nulljs-function-watch-plugin",
640
+ apply: "build",
641
+ config: async (config, { command }) => {
642
+ if (command !== "build") {
643
+ return config;
644
+ }
645
+ const input = {};
646
+ for (const entry of entries) {
647
+ input[entry.name] = entry.path;
648
+ outputToEntry.set(`${entry.name}.js`, entry);
649
+ }
650
+ return {
651
+ build: {
652
+ rollupOptions: {
653
+ input,
654
+ external,
655
+ output: {
656
+ entryFileNames: "[name].js"
657
+ },
658
+ preserveEntrySignatures: "strict"
659
+ }
660
+ }
661
+ };
662
+ },
663
+ async writeBundle(_, bundle) {
664
+ const results = [];
665
+ for (const [fileName, chunk] of Object.entries(bundle)) {
666
+ if (chunk.type !== "chunk" || !chunk.isEntry)
667
+ continue;
668
+ const entry = outputToEntry.get(fileName);
669
+ if (!entry)
670
+ continue;
671
+ results.push({ entry, code: chunk.code });
672
+ }
673
+ await callbacks.onBuildComplete(results);
674
+ }
675
+ };
676
+ }, functionWatchConfig = (entries, callbacks) => {
677
+ return {
678
+ logLevel: "error",
679
+ plugins: [jsFunctionWatch(entries, callbacks)],
680
+ build: {
681
+ outDir: "/tmp/nulljs-dev",
682
+ minify: false,
683
+ watch: {}
684
+ }
685
+ };
686
+ };
687
+ var init_function = __esm(() => {
688
+ init_external();
689
+ });
690
+
691
+ // src/lib/bundle/index.ts
692
+ var isReact = (file) => file.endsWith(".tsx");
693
+ var init_bundle = __esm(() => {
694
+ init_react();
695
+ init_function();
696
+ });
697
+
698
+ // src/lib/deploy.ts
699
+ var exports_deploy = {};
700
+ __export(exports_deploy, {
701
+ deployReact: () => deployReact,
702
+ deployFunction: () => deployFunction,
703
+ clearDeployCache: () => clearDeployCache
704
+ });
705
+ import { basename as basename2 } from "path";
706
+ import { build } from "vite";
707
+ var deployedHashes, clearDeployCache = () => {
708
+ deployedHashes.clear();
709
+ }, hashCode = (code) => {
710
+ return Bun.hash(code).toString(16);
711
+ }, deployFunction = async (filePath, privateKey, apiUrl) => {
712
+ const fileName = basename2(filePath);
713
+ const result = await build(functionConfig(filePath));
714
+ const handler = result.output.find((output) => output.type === "chunk" && output.fileName === "handler.js");
715
+ if (!handler) {
716
+ throw new Error("Bundle failed: handler.js not found in output");
717
+ }
718
+ const hash = hashCode(handler.code);
719
+ const lastHash = deployedHashes.get(filePath);
720
+ if (lastHash === hash) {
721
+ return { deployed: false, skipped: true };
722
+ }
723
+ await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl });
724
+ deployedHashes.set(filePath, hash);
725
+ return { deployed: true, skipped: false };
726
+ }, deployReact = async (filePath, privateKey, apiUrl) => {
727
+ const fileName = basename2(filePath);
728
+ const configResult = await build(spaConfigConfig(filePath));
729
+ const configOutput = configResult.output.find((output) => output.type === "chunk" && output.fileName === "index.js");
730
+ if (!configOutput) {
731
+ throw new Error("Config build failed: config file not found in output");
732
+ }
733
+ const result = await build(spaClientConfig(filePath));
734
+ const assets3 = [];
735
+ assets3.push({ fileName: "config.js", code: configOutput.code });
736
+ for (const output of result.output) {
737
+ if (output.type === "chunk") {
738
+ assets3.push({ fileName: output.fileName, code: output.code });
739
+ } else if (output.type === "asset" && typeof output.source === "string") {
740
+ assets3.push({ fileName: output.fileName, code: output.source });
741
+ }
742
+ }
743
+ const combinedCode = assets3.map((a) => a.code).join("");
744
+ const hash = hashCode(combinedCode);
745
+ const lastHash = deployedHashes.get(filePath);
746
+ if (lastHash === hash) {
747
+ return { deployed: false, skipped: true };
748
+ }
749
+ await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl });
750
+ deployedHashes.set(filePath, hash);
751
+ return { deployed: true, skipped: false };
752
+ };
753
+ var init_deploy = __esm(() => {
754
+ init_api();
755
+ init_bundle();
756
+ deployedHashes = new Map;
757
+ });
758
+
759
+ // src/lib/watcher.ts
760
+ import { watch, existsSync as existsSync3 } from "fs";
761
+ import { readdir } from "fs/promises";
762
+ import { join as join3 } from "path";
763
+ import { build as build2 } from "vite";
764
+ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
765
+ return Bun.hash(code).toString(16);
766
+ }, findFunctionEntries = async (functionsPath) => {
767
+ const entries = [];
768
+ for (const dir of FUNCTION_DIRS) {
769
+ const dirPath = join3(functionsPath, dir);
770
+ try {
771
+ const items = await readdir(dirPath, { withFileTypes: true });
772
+ for (const item of items) {
773
+ if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
774
+ const fullPath = join3(dirPath, item.name);
775
+ const name = `${dir}/${item.name.replace(/\.tsx?$/, "")}`;
776
+ entries.push({
777
+ name,
778
+ path: fullPath,
779
+ type: dir
780
+ });
781
+ }
782
+ }
783
+ } catch {}
784
+ }
785
+ return entries;
786
+ }, startWatcher = async (srcPath, options = {}) => {
787
+ const { silent = false, onDeploy, onDeployBatch, onReady } = options;
788
+ const config = readLocalConfig();
789
+ if (!config) {
790
+ if (!silent)
791
+ console.error('No local configuration found. Run "nulljs dev" first.');
792
+ return () => {};
793
+ }
794
+ const functionsPath = join3(srcPath, "function");
795
+ let entries = await findFunctionEntries(functionsPath);
796
+ let viteWatcher = null;
797
+ let isRestarting = false;
798
+ onReady?.(entries.length);
799
+ const privateKey = await loadPrivateKey(config);
800
+ const deployBatch = async (builds) => {
801
+ const results = [];
802
+ for (const { entry, code } of builds) {
803
+ const hash = hashCode2(code);
804
+ const lastHash = deployedHashes2.get(entry.name);
805
+ if (lastHash === hash) {
806
+ continue;
807
+ }
808
+ try {
809
+ await createFunctionDeployment({
810
+ name: `${entry.name}.ts`,
811
+ assets: [{ fileName: "handler.js", code }]
812
+ }, { privateKey, url: config.api });
813
+ deployedHashes2.set(entry.name, hash);
814
+ results.push({ name: entry.name, success: true, skipped: false });
815
+ } catch (error) {
816
+ results.push({
817
+ name: entry.name,
818
+ success: false,
819
+ skipped: false,
820
+ error: error instanceof Error ? error.message : String(error)
821
+ });
822
+ }
823
+ }
824
+ return results;
825
+ };
826
+ const startViteWatcher = async () => {
827
+ if (entries.length === 0) {
828
+ return null;
829
+ }
830
+ const result = await build2(functionWatchConfig(entries, {
831
+ onBuildComplete: async (builds) => {
832
+ const results = await deployBatch(builds);
833
+ if (results.length > 0) {
834
+ onDeployBatch?.(results);
835
+ for (const r of results) {
836
+ onDeploy?.(r.name, r.success, r.skipped, r.error);
837
+ }
838
+ }
839
+ }
840
+ }));
841
+ if ("on" in result) {
842
+ return result;
843
+ }
844
+ return null;
845
+ };
846
+ const restartViteWatcher = async () => {
847
+ if (isRestarting)
848
+ return;
849
+ isRestarting = true;
850
+ try {
851
+ if (viteWatcher) {
852
+ await viteWatcher.close();
853
+ viteWatcher = null;
854
+ }
855
+ entries = await findFunctionEntries(functionsPath);
856
+ onReady?.(entries.length);
857
+ viteWatcher = await startViteWatcher();
858
+ } finally {
859
+ isRestarting = false;
860
+ }
861
+ };
862
+ viteWatcher = await startViteWatcher();
863
+ const dirWatchers = [];
864
+ for (const dir of FUNCTION_DIRS) {
865
+ const dirPath = join3(functionsPath, dir);
866
+ if (!existsSync3(dirPath))
867
+ continue;
868
+ try {
869
+ const watcher = watch(dirPath, async (eventType, filename) => {
870
+ if (!filename)
871
+ return;
872
+ if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
873
+ return;
874
+ const fullPath = join3(dirPath, filename);
875
+ const entryName = `${dir}/${filename.replace(/\.tsx?$/, "")}`;
876
+ const existingEntry = entries.find((e) => e.name === entryName);
877
+ const fileExists = existsSync3(fullPath);
878
+ if (!existingEntry && fileExists || existingEntry && !fileExists) {
879
+ await restartViteWatcher();
880
+ }
881
+ });
882
+ dirWatchers.push(watcher);
883
+ } catch {}
884
+ }
885
+ return () => {
886
+ for (const watcher of dirWatchers) {
887
+ watcher.close();
888
+ }
889
+ if (viteWatcher) {
890
+ viteWatcher.close();
891
+ }
892
+ };
893
+ }, forceDeployAll = async (srcPath, options = {}) => {
894
+ const { onDeploy, onComplete } = options;
895
+ const config = readLocalConfig();
896
+ if (!config) {
897
+ throw new Error('No local configuration found. Run "nulljs dev" first.');
898
+ }
899
+ const privateKey = await loadPrivateKey(config);
900
+ deployedHashes2.clear();
901
+ const functionsPath = join3(srcPath, "function");
902
+ const entries = await findFunctionEntries(functionsPath);
903
+ const reactEntry = join3(srcPath, "index.tsx");
904
+ const hasReact = existsSync3(reactEntry);
905
+ let total = entries.length + (hasReact ? 1 : 0);
906
+ let successful = 0;
907
+ let failed = 0;
908
+ const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (init_deploy(), exports_deploy));
909
+ clearDeployCache2();
910
+ for (const entry of entries) {
911
+ try {
912
+ await deployFunction2(entry.path, privateKey, config.api);
913
+ successful++;
914
+ onDeploy?.(entry.name, true);
915
+ } catch (error) {
916
+ failed++;
917
+ onDeploy?.(entry.name, false, error instanceof Error ? error.message : String(error));
918
+ }
919
+ }
920
+ if (hasReact) {
921
+ const fileName = "index.tsx";
922
+ try {
923
+ await deployReact2(reactEntry, privateKey, config.api);
924
+ successful++;
925
+ onDeploy?.(fileName, true);
926
+ } catch (error) {
927
+ failed++;
928
+ onDeploy?.(fileName, false, error instanceof Error ? error.message : String(error));
929
+ }
930
+ }
931
+ onComplete?.(total, successful, failed);
932
+ };
933
+ var init_watcher = __esm(() => {
934
+ init_config();
935
+ init_bundle();
936
+ init_api();
937
+ FUNCTION_DIRS = ["api", "cron", "event"];
938
+ deployedHashes2 = new Map;
939
+ });
940
+
941
+ // src/lib/vite.ts
942
+ import { createServer } from "vite";
943
+ import { dirname as dirname2, resolve, basename as basename3 } from "path";
944
+ import react3 from "@vitejs/plugin-react";
945
+ import tailwindcss2 from "@tailwindcss/vite";
946
+ import { writeFile, unlink } from "fs/promises";
947
+ import { existsSync as existsSync4 } from "fs";
948
+ var VITE_PORT = 5173, GATEWAY_PORT = 3001, createViteLogger = (onLog) => {
949
+ return {
950
+ info: (msg) => onLog?.(msg, "info"),
951
+ warn: (msg) => onLog?.(msg, "warn"),
952
+ warnOnce: (msg) => onLog?.(msg, "warn"),
953
+ error: (msg) => onLog?.(msg, "error"),
954
+ clearScreen: () => {},
955
+ hasErrorLogged: () => false,
956
+ hasWarned: false
957
+ };
958
+ }, createTempIndexHtml = (componentPath, projectRoot) => {
959
+ const relativePath = resolve(componentPath).replace(projectRoot + "/", "");
960
+ const componentName = basename3(componentPath, ".tsx");
961
+ return `<!DOCTYPE html>
962
+ <html lang="en">
963
+ <head>
964
+ <meta charset="UTF-8" />
965
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
966
+ <title>${componentName} - Dev Mode</title>
967
+ <style>
968
+ * { box-sizing: border-box; }
969
+ body { margin: 0; padding: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
970
+ #root { min-height: 100vh; }
971
+ </style>
972
+ </head>
973
+ <body>
974
+ <div id="root"></div>
975
+ <script type="module">
976
+ import React from 'react'
977
+ import ReactDOM from 'react-dom/client'
978
+ import { Page } from '/${relativePath}'
979
+
980
+ const root = ReactDOM.createRoot(document.getElementById('root'))
981
+ root.render(
982
+ React.createElement(React.StrictMode, null,
983
+ React.createElement(Page)
984
+ )
985
+ )
986
+ </script>
987
+ </body>
988
+ </html>`;
989
+ }, startViteServer = async (options) => {
990
+ const indexTsxPath = resolve(options.srcDir, "index.tsx");
991
+ if (!existsSync4(indexTsxPath)) {
992
+ return null;
993
+ }
994
+ const projectDir = dirname2(options.srcDir);
995
+ const tempIndexPath = resolve(projectDir, "index.html");
996
+ try {
997
+ const indexHtml = createTempIndexHtml(indexTsxPath, projectDir);
998
+ await writeFile(tempIndexPath, indexHtml);
999
+ const server = await createServer({
1000
+ root: projectDir,
1001
+ plugins: [react3(), tailwindcss2()],
1002
+ customLogger: createViteLogger(options.onLog),
1003
+ server: {
1004
+ port: VITE_PORT,
1005
+ host: true,
1006
+ proxy: {
1007
+ "/api": {
1008
+ target: `http://localhost:${GATEWAY_PORT}`,
1009
+ changeOrigin: true,
1010
+ secure: false
1011
+ },
1012
+ "/assets": {
1013
+ target: `http://localhost:${GATEWAY_PORT}`,
1014
+ changeOrigin: true,
1015
+ secure: false
1016
+ }
1017
+ }
1018
+ }
1019
+ });
1020
+ await server.listen();
1021
+ options.onLog?.(`Vite server started on http://localhost:${VITE_PORT}`, "info");
1022
+ return { server, indexHtmlPath: tempIndexPath };
1023
+ } catch (error) {
1024
+ options.onLog?.(`Failed to start Vite: ${error}`, "error");
1025
+ return null;
1026
+ }
1027
+ }, stopViteServer = async (info) => {
1028
+ await info.server.close();
1029
+ try {
1030
+ await unlink(info.indexHtmlPath);
1031
+ } catch {}
1032
+ };
1033
+ var init_vite = () => {};
1034
+
1035
+ // src/components/DeploymentLogsPane.tsx
1036
+ import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
1037
+ var getLevelColor = (level) => {
1038
+ switch (level.toLowerCase()) {
1039
+ case "error":
1040
+ return "red";
1041
+ case "warn":
1042
+ case "warning":
1043
+ return "yellow";
1044
+ case "info":
1045
+ return "cyan";
1046
+ case "debug":
1047
+ return "gray";
1048
+ default:
1049
+ return "white";
1050
+ }
1051
+ }, sanitizeMessage = (message) => {
1052
+ return message.replace(/\x1b\[[0-9;]*m/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
1053
+ }, DeploymentLogsPane = (props) => {
1054
+ if (props.loading && props.logs.length === 0) {
1055
+ return /* @__PURE__ */ jsxDEV("box", {
1056
+ flexDirection: "column",
1057
+ padding: 1,
1058
+ children: /* @__PURE__ */ jsxDEV("text", {
1059
+ children: "Loading deployment logs..."
1060
+ }, undefined, false, undefined, this)
1061
+ }, undefined, false, undefined, this);
1062
+ }
1063
+ if (!props.loading && props.logs.length === 0) {
1064
+ return /* @__PURE__ */ jsxDEV("box", {
1065
+ flexDirection: "column",
1066
+ padding: 1,
1067
+ children: /* @__PURE__ */ jsxDEV("text", {
1068
+ children: "No deployment logs found"
1069
+ }, undefined, false, undefined, this)
1070
+ }, undefined, false, undefined, this);
1071
+ }
1072
+ const logsInOrder = [...props.logs].reverse();
1073
+ return /* @__PURE__ */ jsxDEV("scrollbox", {
1074
+ focused: true,
1075
+ stickyStart: props.autoScroll ? "bottom" : "top",
1076
+ stickyScroll: props.autoScroll,
1077
+ children: logsInOrder.map((log) => {
1078
+ const time = new Date(log.time).toLocaleTimeString();
1079
+ const timestamp = `[${time}]`;
1080
+ const level = log.level.toUpperCase();
1081
+ return /* @__PURE__ */ jsxDEV("box", {
1082
+ flexDirection: "row",
1083
+ children: /* @__PURE__ */ jsxDEV("text", {
1084
+ children: [
1085
+ /* @__PURE__ */ jsxDEV("span", {
1086
+ fg: "gray",
1087
+ children: timestamp
1088
+ }, undefined, false, undefined, this),
1089
+ /* @__PURE__ */ jsxDEV("span", {
1090
+ fg: getLevelColor(log.level),
1091
+ children: ` ${level} `
1092
+ }, undefined, false, undefined, this),
1093
+ /* @__PURE__ */ jsxDEV("span", {
1094
+ fg: "blue",
1095
+ children: `(${log.deployment_name})`
1096
+ }, undefined, false, undefined, this),
1097
+ /* @__PURE__ */ jsxDEV("span", {
1098
+ fg: "cyan",
1099
+ children: ` ${log.trigger} `
1100
+ }, undefined, false, undefined, this),
1101
+ /* @__PURE__ */ jsxDEV("span", {
1102
+ children: sanitizeMessage(log.message)
1103
+ }, undefined, false, undefined, this)
1104
+ ]
1105
+ }, undefined, true, undefined, this)
1106
+ }, log.id, false, undefined, this);
1107
+ })
1108
+ }, `${props.autoScroll}-${props.jumpTrigger}`, false, undefined, this);
1109
+ };
1110
+ var init_DeploymentLogsPane = () => {};
1111
+
1112
+ // src/components/SystemLogsPane.tsx
1113
+ import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
1114
+ var getLevelColor2 = (level) => {
1115
+ switch (level.toLowerCase()) {
1116
+ case "error":
1117
+ return "red";
1118
+ case "warn":
1119
+ case "warning":
1120
+ return "yellow";
1121
+ case "info":
1122
+ return "green";
1123
+ case "debug":
1124
+ case "trace":
1125
+ return "gray";
1126
+ default:
1127
+ return "white";
1128
+ }
1129
+ }, sanitizeMessage2 = (message) => {
1130
+ return message.replace(/\x1b\[[0-9;]*m/g, "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
1131
+ }, SystemLogsPane = (props) => {
1132
+ if (props.loading && props.logs.length === 0) {
1133
+ return /* @__PURE__ */ jsxDEV2("box", {
1134
+ flexDirection: "column",
1135
+ padding: 1,
1136
+ children: /* @__PURE__ */ jsxDEV2("text", {
1137
+ children: "Loading system logs..."
1138
+ }, undefined, false, undefined, this)
1139
+ }, undefined, false, undefined, this);
1140
+ }
1141
+ if (!props.loading && props.logs.length === 0) {
1142
+ return /* @__PURE__ */ jsxDEV2("box", {
1143
+ flexDirection: "column",
1144
+ padding: 1,
1145
+ children: /* @__PURE__ */ jsxDEV2("text", {
1146
+ children: "No system logs found"
1147
+ }, undefined, false, undefined, this)
1148
+ }, undefined, false, undefined, this);
1149
+ }
1150
+ const logsInOrder = [...props.logs].reverse();
1151
+ return /* @__PURE__ */ jsxDEV2("scrollbox", {
1152
+ focused: true,
1153
+ stickyStart: props.autoScroll ? "bottom" : "top",
1154
+ stickyScroll: props.autoScroll,
1155
+ children: logsInOrder.map((log) => {
1156
+ const time = new Date(log.timestamp).toLocaleTimeString();
1157
+ const timestamp = `[${time}]`;
1158
+ const level = log.level.toUpperCase();
1159
+ return /* @__PURE__ */ jsxDEV2("box", {
1160
+ flexDirection: "row",
1161
+ children: /* @__PURE__ */ jsxDEV2("text", {
1162
+ children: [
1163
+ /* @__PURE__ */ jsxDEV2("span", {
1164
+ fg: "gray",
1165
+ children: timestamp
1166
+ }, undefined, false, undefined, this),
1167
+ /* @__PURE__ */ jsxDEV2("span", {
1168
+ fg: getLevelColor2(log.level),
1169
+ children: ` ${level} `
1170
+ }, undefined, false, undefined, this),
1171
+ /* @__PURE__ */ jsxDEV2("span", {
1172
+ children: sanitizeMessage2(log.message)
1173
+ }, undefined, false, undefined, this)
1174
+ ]
1175
+ }, undefined, true, undefined, this)
1176
+ }, log.id, false, undefined, this);
1177
+ })
1178
+ }, `${props.autoScroll}-${props.jumpTrigger}`, false, undefined, this);
1179
+ };
1180
+ var init_SystemLogsPane = () => {};
1181
+
1182
+ // src/components/DeployAnimation.tsx
1183
+ import { useEffect, useState, useRef } from "react";
1184
+ import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
1185
+ var DeployAnimation = (props) => {
1186
+ const [currentIndex, setCurrentIndex] = useState(0);
1187
+ const [progress, setProgress] = useState(0);
1188
+ const intervalRef = useRef(null);
1189
+ const completedRef = useRef(false);
1190
+ const duration = 400;
1191
+ const frameRate = 16;
1192
+ useEffect(() => {
1193
+ if (props.deployed.length === 0) {
1194
+ props.onComplete();
1195
+ return;
1196
+ }
1197
+ if (completedRef.current) {
1198
+ return;
1199
+ }
1200
+ setProgress(0);
1201
+ const startTime = Date.now();
1202
+ intervalRef.current = setInterval(() => {
1203
+ const elapsed = Date.now() - startTime;
1204
+ const newProgress = Math.min(100, elapsed / duration * 100);
1205
+ setProgress(newProgress);
1206
+ if (newProgress >= 100) {
1207
+ if (intervalRef.current) {
1208
+ clearInterval(intervalRef.current);
1209
+ intervalRef.current = null;
1210
+ }
1211
+ setTimeout(() => {
1212
+ if (currentIndex >= props.deployed.length - 1) {
1213
+ completedRef.current = true;
1214
+ props.onComplete();
1215
+ } else {
1216
+ setCurrentIndex((prev) => prev + 1);
1217
+ }
1218
+ }, 150);
1219
+ }
1220
+ }, frameRate);
1221
+ return () => {
1222
+ if (intervalRef.current) {
1223
+ clearInterval(intervalRef.current);
1224
+ }
1225
+ };
1226
+ }, [currentIndex, props.deployed.length, props.onComplete]);
1227
+ const current = props.deployed[currentIndex];
1228
+ if (props.deployed.length === 0 || !current || completedRef.current) {
1229
+ return null;
1230
+ }
1231
+ const barWidth = 8;
1232
+ const filled = Math.round(progress / 100 * barWidth);
1233
+ const empty = barWidth - filled;
1234
+ const bar = "\u2501".repeat(filled) + "\u2500".repeat(empty);
1235
+ return /* @__PURE__ */ jsxDEV3("box", {
1236
+ backgroundColor: "black",
1237
+ flexDirection: "row",
1238
+ paddingLeft: 1,
1239
+ paddingRight: 1,
1240
+ children: /* @__PURE__ */ jsxDEV3("text", {
1241
+ children: [
1242
+ /* @__PURE__ */ jsxDEV3("span", {
1243
+ fg: current.success ? "#22c55e" : "#ef4444",
1244
+ children: [
1245
+ bar,
1246
+ " ",
1247
+ current.success ? "\u2713" : "\u2717"
1248
+ ]
1249
+ }, undefined, true, undefined, this),
1250
+ /* @__PURE__ */ jsxDEV3("span", {
1251
+ children: [
1252
+ " ",
1253
+ current.name
1254
+ ]
1255
+ }, undefined, true, undefined, this),
1256
+ props.deployed.length > 1 && /* @__PURE__ */ jsxDEV3("span", {
1257
+ fg: "#666",
1258
+ children: [
1259
+ " ",
1260
+ "(",
1261
+ currentIndex + 1,
1262
+ "/",
1263
+ props.deployed.length,
1264
+ ")"
1265
+ ]
1266
+ }, undefined, true, undefined, this)
1267
+ ]
1268
+ }, undefined, true, undefined, this)
1269
+ }, undefined, false, undefined, this);
1270
+ };
1271
+ var init_DeployAnimation = () => {};
1272
+
1273
+ // src/components/Header.tsx
1274
+ import { jsxDEV as jsxDEV4, Fragment } from "@opentui/react/jsx-dev-runtime";
1275
+ var getBinarySourceLabel = (source) => {
1276
+ switch (source) {
1277
+ case "local-debug":
1278
+ return "local (debug)";
1279
+ case "local-release":
1280
+ return "local (release)";
1281
+ case "local-env":
1282
+ return "local (env)";
1283
+ case "npm":
1284
+ return "npm";
1285
+ }
1286
+ }, Header = (props) => {
1287
+ const { activeTab, functionCount, viteRunning, isDeploying, binarySource } = props;
1288
+ return /* @__PURE__ */ jsxDEV4("box", {
1289
+ flexDirection: "row",
1290
+ justifyContent: "space-between",
1291
+ children: [
1292
+ /* @__PURE__ */ jsxDEV4("box", {
1293
+ flexDirection: "row",
1294
+ children: [
1295
+ /* @__PURE__ */ jsxDEV4("text", {
1296
+ fg: activeTab === "system" ? "cyan" : undefined,
1297
+ content: "[1] System Logs"
1298
+ }, undefined, false, undefined, this),
1299
+ /* @__PURE__ */ jsxDEV4("text", {
1300
+ content: " | "
1301
+ }, undefined, false, undefined, this),
1302
+ /* @__PURE__ */ jsxDEV4("text", {
1303
+ fg: activeTab === "deployment" ? "cyan" : undefined,
1304
+ content: "[2] Deployment Logs"
1305
+ }, undefined, false, undefined, this)
1306
+ ]
1307
+ }, undefined, true, undefined, this),
1308
+ /* @__PURE__ */ jsxDEV4("box", {
1309
+ flexDirection: "row",
1310
+ children: [
1311
+ isDeploying ? /* @__PURE__ */ jsxDEV4("text", {
1312
+ fg: "yellow",
1313
+ content: "\u25CF deploying all..."
1314
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4("text", {
1315
+ fg: functionCount > 0 ? "green" : "yellow",
1316
+ content: "\u25CF watching"
1317
+ }, undefined, false, undefined, this),
1318
+ /* @__PURE__ */ jsxDEV4("text", {
1319
+ fg: "gray",
1320
+ content: ` (${functionCount} functions)`
1321
+ }, undefined, false, undefined, this),
1322
+ viteRunning && /* @__PURE__ */ jsxDEV4(Fragment, {
1323
+ children: [
1324
+ /* @__PURE__ */ jsxDEV4("text", {
1325
+ fg: "gray",
1326
+ content: " | vite: "
1327
+ }, undefined, false, undefined, this),
1328
+ /* @__PURE__ */ jsxDEV4("text", {
1329
+ fg: "magenta",
1330
+ content: ":5173"
1331
+ }, undefined, false, undefined, this)
1332
+ ]
1333
+ }, undefined, true, undefined, this),
1334
+ /* @__PURE__ */ jsxDEV4("text", {
1335
+ fg: "gray",
1336
+ content: " | server: "
1337
+ }, undefined, false, undefined, this),
1338
+ /* @__PURE__ */ jsxDEV4("text", {
1339
+ fg: binarySource.startsWith("local") ? "yellow" : "green",
1340
+ content: getBinarySourceLabel(binarySource)
1341
+ }, undefined, false, undefined, this)
1342
+ ]
1343
+ }, undefined, true, undefined, this)
1344
+ ]
1345
+ }, undefined, true, undefined, this);
1346
+ };
1347
+ var init_Header = () => {};
1348
+
1349
+ // src/components/HelpModal.tsx
1350
+ import { jsxDEV as jsxDEV5 } from "@opentui/react/jsx-dev-runtime";
1351
+ var KeyBinding = ({ keys, description }) => /* @__PURE__ */ jsxDEV5("text", {
1352
+ children: [
1353
+ " ",
1354
+ /* @__PURE__ */ jsxDEV5("span", {
1355
+ fg: "green",
1356
+ children: keys
1357
+ }, undefined, false, undefined, this),
1358
+ /* @__PURE__ */ jsxDEV5("span", {
1359
+ fg: "gray",
1360
+ children: " - "
1361
+ }, undefined, false, undefined, this),
1362
+ description
1363
+ ]
1364
+ }, undefined, true, undefined, this), Section = ({ title }) => /* @__PURE__ */ jsxDEV5("text", {
1365
+ children: /* @__PURE__ */ jsxDEV5("span", {
1366
+ fg: "gray",
1367
+ children: title
1368
+ }, undefined, false, undefined, this)
1369
+ }, undefined, false, undefined, this), HelpModal = ({ visible }) => {
1370
+ if (!visible)
1371
+ return null;
1372
+ return /* @__PURE__ */ jsxDEV5("box", {
1373
+ position: "absolute",
1374
+ top: 0,
1375
+ left: 0,
1376
+ width: "100%",
1377
+ height: "100%",
1378
+ backgroundColor: "black",
1379
+ opacity: 0.9,
1380
+ justifyContent: "center",
1381
+ alignItems: "center",
1382
+ children: /* @__PURE__ */ jsxDEV5("box", {
1383
+ width: 50,
1384
+ height: 18,
1385
+ border: true,
1386
+ borderStyle: "rounded",
1387
+ flexDirection: "column",
1388
+ padding: 1,
1389
+ backgroundColor: "black",
1390
+ children: [
1391
+ /* @__PURE__ */ jsxDEV5("text", {
1392
+ children: /* @__PURE__ */ jsxDEV5("span", {
1393
+ fg: "cyan",
1394
+ children: /* @__PURE__ */ jsxDEV5("strong", {
1395
+ children: "Keyboard Shortcuts"
1396
+ }, undefined, false, undefined, this)
1397
+ }, undefined, false, undefined, this)
1398
+ }, undefined, false, undefined, this),
1399
+ /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1400
+ /* @__PURE__ */ jsxDEV5(Section, {
1401
+ title: "Navigation"
1402
+ }, undefined, false, undefined, this),
1403
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1404
+ keys: "1/2",
1405
+ description: "Switch tabs"
1406
+ }, undefined, false, undefined, this),
1407
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1408
+ keys: "h/l",
1409
+ description: "Previous/next tab"
1410
+ }, undefined, false, undefined, this),
1411
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1412
+ keys: "Tab",
1413
+ description: "Cycle tabs"
1414
+ }, undefined, false, undefined, this),
1415
+ /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1416
+ /* @__PURE__ */ jsxDEV5(Section, {
1417
+ title: "Scrolling"
1418
+ }, undefined, false, undefined, this),
1419
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1420
+ keys: "j/k",
1421
+ description: "Scroll down/up"
1422
+ }, undefined, false, undefined, this),
1423
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1424
+ keys: "g/G",
1425
+ description: "Top/bottom"
1426
+ }, undefined, false, undefined, this),
1427
+ /* @__PURE__ */ jsxDEV5("text", {}, undefined, false, undefined, this),
1428
+ /* @__PURE__ */ jsxDEV5(Section, {
1429
+ title: "Actions"
1430
+ }, undefined, false, undefined, this),
1431
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1432
+ keys: "d",
1433
+ description: "Deploy all"
1434
+ }, undefined, false, undefined, this),
1435
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1436
+ keys: "?",
1437
+ description: "Toggle help"
1438
+ }, undefined, false, undefined, this),
1439
+ /* @__PURE__ */ jsxDEV5(KeyBinding, {
1440
+ keys: "Esc",
1441
+ description: "Close help"
1442
+ }, undefined, false, undefined, this)
1443
+ ]
1444
+ }, undefined, true, undefined, this)
1445
+ }, undefined, false, undefined, this);
1446
+ };
1447
+ var init_HelpModal = () => {};
1448
+
1449
+ // src/ui.tsx
1450
+ var exports_ui = {};
1451
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1452
+ import { createCliRenderer } from "@opentui/core";
1453
+ import { createRoot } from "@opentui/react";
1454
+ import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
1455
+ var App = (props) => {
1456
+ const [error, setError] = useState2(null);
1457
+ const [loading, setLoading] = useState2(true);
1458
+ const [deploymentLogs, setDeploymentLogs] = useState2([]);
1459
+ const [systemLogs, setSystemLogs] = useState2([]);
1460
+ const [activeTab, setActiveTab] = useState2("system");
1461
+ const [autoScroll, setAutoScroll] = useState2(true);
1462
+ const [jumpTrigger, setJumpTrigger] = useState2(0);
1463
+ const [watcherStatus, setWatcherStatus] = useState2(null);
1464
+ const [showKeyBindings, setShowKeyBindings] = useState2(false);
1465
+ const [isDeploying, setIsDeploying] = useState2(false);
1466
+ useEffect2(() => {
1467
+ props.onWatcherEvent((event) => {
1468
+ console.error("DEBUG: received watcher event:", event.type, event.deployed?.length);
1469
+ setWatcherStatus(event);
1470
+ });
1471
+ }, [props.onWatcherEvent]);
1472
+ useEffect2(() => {
1473
+ const fn = async () => {
1474
+ try {
1475
+ setLoading(true);
1476
+ setError(null);
1477
+ const [deploymentLogsData, systemLogsData] = await Promise.all([
1478
+ searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
1479
+ getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
1480
+ ]);
1481
+ setDeploymentLogs(deploymentLogsData);
1482
+ setSystemLogs(systemLogsData);
1483
+ } catch (err) {
1484
+ setError(err instanceof Error ? err.message : String(err));
1485
+ console.error("Failed to fetch logs:", err);
1486
+ } finally {
1487
+ setLoading(false);
1488
+ }
1489
+ };
1490
+ fn();
1491
+ }, [props.config.key.public, props.config.api]);
1492
+ useEffect2(() => {
1493
+ const interval = setInterval(async () => {
1494
+ if (loading)
1495
+ return;
1496
+ try {
1497
+ const [newDeploymentLogs, newSystemLogs] = await Promise.all([
1498
+ searchDeploymentLogs(props.config.key.public, { limit: 100 }, props.config.api),
1499
+ getSystemLogs(props.config.key.public, { limit: 100 }, props.config.api)
1500
+ ]);
1501
+ if (newDeploymentLogs.length > 0) {
1502
+ setDeploymentLogs(newDeploymentLogs);
1503
+ }
1504
+ if (newSystemLogs.length > 0) {
1505
+ setSystemLogs(newSystemLogs);
1506
+ }
1507
+ } catch (err) {
1508
+ console.error("Failed to poll for new logs:", err);
1509
+ }
1510
+ }, 2000);
1511
+ return () => clearInterval(interval);
1512
+ }, [loading, props.config.key.public, props.config.api]);
1513
+ useEffect2(() => {
1514
+ const handleKeyPress = (data) => {
1515
+ const key = data.toString();
1516
+ if (key === "1") {
1517
+ setActiveTab("system");
1518
+ return;
1519
+ }
1520
+ if (key === "2") {
1521
+ setActiveTab("deployment");
1522
+ return;
1523
+ }
1524
+ if (key === "\t") {
1525
+ setActiveTab((prev) => prev === "system" ? "deployment" : "system");
1526
+ return;
1527
+ }
1528
+ if (key === "h") {
1529
+ setActiveTab("system");
1530
+ return;
1531
+ }
1532
+ if (key === "l") {
1533
+ setActiveTab("deployment");
1534
+ return;
1535
+ }
1536
+ if (key === "g") {
1537
+ setAutoScroll(false);
1538
+ setJumpTrigger((prev) => prev + 1);
1539
+ return;
1540
+ }
1541
+ if (key === "G") {
1542
+ setAutoScroll(true);
1543
+ setJumpTrigger((prev) => prev + 1);
1544
+ return;
1545
+ }
1546
+ if (key === "?") {
1547
+ setShowKeyBindings((prev) => !prev);
1548
+ return;
1549
+ }
1550
+ if (key === "\x1B") {
1551
+ setShowKeyBindings(false);
1552
+ return;
1553
+ }
1554
+ if (key === "d" && !isDeploying) {
1555
+ setIsDeploying(true);
1556
+ props.onForceDeploy().finally(() => {
1557
+ setIsDeploying(false);
1558
+ });
1559
+ return;
1560
+ }
1561
+ };
1562
+ process.stdin.setRawMode(true);
1563
+ process.stdin.on("data", handleKeyPress);
1564
+ return () => {
1565
+ process.stdin.setRawMode(false);
1566
+ process.stdin.off("data", handleKeyPress);
1567
+ };
1568
+ }, [isDeploying, props.onForceDeploy]);
1569
+ if (error) {
1570
+ return /* @__PURE__ */ jsxDEV6("box", {
1571
+ flexGrow: 1,
1572
+ flexDirection: "column",
1573
+ padding: 1,
1574
+ children: /* @__PURE__ */ jsxDEV6("text", {
1575
+ children: /* @__PURE__ */ jsxDEV6("span", {
1576
+ fg: "red",
1577
+ children: [
1578
+ "Error: ",
1579
+ error
1580
+ ]
1581
+ }, undefined, true, undefined, this)
1582
+ }, undefined, false, undefined, this)
1583
+ }, undefined, false, undefined, this);
1584
+ }
1585
+ return /* @__PURE__ */ jsxDEV6("box", {
1586
+ flexDirection: "column",
1587
+ children: [
1588
+ /* @__PURE__ */ jsxDEV6(Header, {
1589
+ activeTab,
1590
+ functionCount: props.functionCount,
1591
+ viteRunning: props.viteRunning,
1592
+ isDeploying,
1593
+ binarySource: props.binarySource
1594
+ }, undefined, false, undefined, this),
1595
+ /* @__PURE__ */ jsxDEV6("box", {
1596
+ paddingTop: 1,
1597
+ flexDirection: "column",
1598
+ children: [
1599
+ activeTab === "system" ? /* @__PURE__ */ jsxDEV6(SystemLogsPane, {
1600
+ logs: systemLogs,
1601
+ loading,
1602
+ autoScroll,
1603
+ jumpTrigger
1604
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV6(DeploymentLogsPane, {
1605
+ logs: deploymentLogs,
1606
+ loading,
1607
+ autoScroll,
1608
+ jumpTrigger
1609
+ }, undefined, false, undefined, this),
1610
+ watcherStatus?.type === "deploy" && watcherStatus.deployed && watcherStatus.deployed.length > 0 && /* @__PURE__ */ jsxDEV6(DeployAnimation, {
1611
+ deployed: watcherStatus.deployed,
1612
+ onComplete: () => {
1613
+ console.error("DEBUG: animation complete");
1614
+ setWatcherStatus(null);
1615
+ }
1616
+ }, undefined, false, undefined, this)
1617
+ ]
1618
+ }, undefined, true, undefined, this),
1619
+ /* @__PURE__ */ jsxDEV6(HelpModal, {
1620
+ visible: showKeyBindings
1621
+ }, undefined, false, undefined, this)
1622
+ ]
1623
+ }, undefined, true, undefined, this);
1624
+ }, main = async () => {
1625
+ let serverInfo = null;
1626
+ let viteInfo = null;
1627
+ let stopWatcher = null;
1628
+ let isCleaningUp = false;
1629
+ const cleanup = async (exitAfter = true) => {
1630
+ if (isCleaningUp)
1631
+ return;
1632
+ isCleaningUp = true;
1633
+ if (stopWatcher) {
1634
+ stopWatcher();
1635
+ stopWatcher = null;
1636
+ }
1637
+ if (viteInfo) {
1638
+ await stopViteServer(viteInfo);
1639
+ viteInfo = null;
1640
+ }
1641
+ if (serverInfo) {
1642
+ const pid = serverInfo.process.pid;
1643
+ serverInfo = null;
1644
+ try {
1645
+ process.kill(pid, "SIGTERM");
1646
+ } catch {}
1647
+ }
1648
+ if (exitAfter) {
1649
+ process.exit(0);
1650
+ }
1651
+ };
1652
+ process.on("SIGINT", () => cleanup(true));
1653
+ process.on("SIGTERM", () => cleanup(true));
1654
+ process.on("exit", () => cleanup(false));
1655
+ try {
1656
+ serverInfo = await startServer();
1657
+ const devConfig = await getOrCreateLocalDevConfig();
1658
+ const srcPath = process.cwd() + "/src";
1659
+ viteInfo = await startViteServer({
1660
+ srcDir: srcPath,
1661
+ onLog: (_msg, _level) => {}
1662
+ });
1663
+ let watcherEventHandler = null;
1664
+ let pendingEvents = [];
1665
+ let functionCount = 0;
1666
+ const sendEvent = (event) => {
1667
+ if (watcherEventHandler) {
1668
+ watcherEventHandler(event);
1669
+ } else {
1670
+ pendingEvents.push(event);
1671
+ }
1672
+ };
1673
+ const renderer = await createCliRenderer();
1674
+ const root = createRoot(renderer);
1675
+ const handleForceDeploy = async () => {
1676
+ const deployResults = [];
1677
+ await forceDeployAll(srcPath, {
1678
+ onDeploy: (filePath, success, error) => {
1679
+ deployResults.push({ name: filePath, success, error });
1680
+ }
1681
+ });
1682
+ if (deployResults.length > 0) {
1683
+ sendEvent({
1684
+ type: "deploy",
1685
+ deployed: deployResults,
1686
+ timestamp: new Date
1687
+ });
1688
+ }
1689
+ };
1690
+ stopWatcher = await startWatcher(srcPath, {
1691
+ silent: true,
1692
+ onReady: (count) => {
1693
+ functionCount = count;
1694
+ },
1695
+ onDeployBatch: (results) => {
1696
+ sendEvent({
1697
+ type: "deploy",
1698
+ deployed: results.map((r) => ({
1699
+ name: r.name,
1700
+ success: r.success,
1701
+ error: r.error
1702
+ })),
1703
+ timestamp: new Date
1704
+ });
1705
+ }
1706
+ });
1707
+ root.render(/* @__PURE__ */ jsxDEV6(App, {
1708
+ config: devConfig,
1709
+ binarySource: serverInfo.binarySource,
1710
+ functionCount,
1711
+ viteRunning: viteInfo !== null,
1712
+ onWatcherEvent: (handler) => {
1713
+ watcherEventHandler = handler;
1714
+ if (pendingEvents.length > 0) {
1715
+ for (const event of pendingEvents) {
1716
+ handler(event);
1717
+ }
1718
+ pendingEvents = [];
1719
+ }
1720
+ },
1721
+ onForceDeploy: handleForceDeploy
1722
+ }, undefined, false, undefined, this));
1723
+ } catch (error) {
1724
+ console.error("Failed to start server:", error);
1725
+ cleanup();
1726
+ }
1727
+ };
1728
+ var init_ui = __esm(() => {
1729
+ init_api();
1730
+ init_server();
1731
+ init_watcher();
1732
+ init_vite();
1733
+ init_config();
1734
+ init_DeploymentLogsPane();
1735
+ init_SystemLogsPane();
1736
+ init_DeployAnimation();
1737
+ init_Header();
1738
+ init_HelpModal();
1739
+ main();
1740
+ });
1741
+
1742
+ // src/cli.ts
1743
+ import { Command as Command3 } from "commander";
1744
+
1745
+ // src/commands/dev.ts
1746
+ var registerDevCommand = (program) => {
1747
+ program.command("dev").description("Launch the development renderer").action(async () => {
1748
+ await Promise.resolve().then(() => (init_ui(), exports_ui));
1749
+ });
1750
+ };
1751
+ // src/commands/deploy.ts
1752
+ init_config();
1753
+ init_bundle();
1754
+ init_deploy();
1755
+ import chalk3 from "chalk";
1756
+ import { basename as basename4, resolve as resolve2, join as join4 } from "path";
1757
+ import { existsSync as existsSync5 } from "fs";
1758
+ import { readdir as readdir2 } from "fs/promises";
1759
+ import * as p from "@clack/prompts";
1760
+ var FUNCTION_DIRS2 = ["api", "cron", "event"];
1761
+ var selectConfig = async () => {
1762
+ const configList = listConfigs();
1763
+ if (!configList || configList.configs.length === 0) {
1764
+ return null;
1765
+ }
1766
+ if (configList.configs.length === 1) {
1767
+ return configList.configs[0] ?? null;
1768
+ }
1769
+ const selected = await p.select({
1770
+ message: "Select config to deploy to",
1771
+ options: configList.configs.map((c) => ({
1772
+ value: c.name,
1773
+ label: c.name,
1774
+ hint: c.name === configList.current ? "current" : c.api
1775
+ })),
1776
+ initialValue: configList.current
1777
+ });
1778
+ if (p.isCancel(selected)) {
1779
+ p.cancel("Deployment cancelled");
1780
+ process.exit(0);
1781
+ }
1782
+ return getConfig(selected);
1783
+ };
1784
+ var findAllDeployables = async (srcPath) => {
1785
+ const functions = [];
1786
+ const functionsPath = join4(srcPath, "function");
1787
+ for (const dir of FUNCTION_DIRS2) {
1788
+ const dirPath = join4(functionsPath, dir);
1789
+ try {
1790
+ const items = await readdir2(dirPath, { withFileTypes: true });
1791
+ for (const item of items) {
1792
+ if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
1793
+ functions.push(join4(dirPath, item.name));
1794
+ }
1795
+ }
1796
+ } catch {}
1797
+ }
1798
+ const reactEntry = join4(srcPath, "index.tsx");
1799
+ const hasReact = existsSync5(reactEntry);
1800
+ return { functions, reactEntry: hasReact ? reactEntry : null };
1801
+ };
1802
+ var deployAll = async (config, options) => {
1803
+ const srcPath = join4(process.cwd(), "src");
1804
+ if (!existsSync5(srcPath)) {
1805
+ console.error(chalk3.red("\u2717 No src directory found"));
1806
+ process.exit(1);
1807
+ }
1808
+ const { functions, reactEntry } = await findAllDeployables(srcPath);
1809
+ const total = functions.length + (reactEntry ? 1 : 0);
1810
+ if (total === 0) {
1811
+ console.log(chalk3.yellow("No functions or React app found to deploy"));
1812
+ return;
1813
+ }
1814
+ if (options.force) {
1815
+ clearDeployCache();
1816
+ }
1817
+ const privateKey = await loadPrivateKey(config);
1818
+ let successful = 0;
1819
+ let failed = 0;
1820
+ let skipped = 0;
1821
+ console.log(chalk3.cyan(`Deploying ${total} file(s) to ${config.name}...
1822
+ `));
1823
+ for (const filePath of functions) {
1824
+ const fileName = basename4(filePath);
1825
+ try {
1826
+ console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
1827
+ const result = await deployFunction(filePath, privateKey, config.api);
1828
+ if (result.skipped) {
1829
+ console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1830
+ skipped++;
1831
+ } else {
1832
+ console.log(chalk3.green("\u2713 Deployed ") + chalk3.bold(fileName));
1833
+ successful++;
1834
+ }
1835
+ } catch (error) {
1836
+ console.error(chalk3.red("\u2717 Failed ") + chalk3.bold(fileName) + chalk3.red(`: ${error instanceof Error ? error.message : error}`));
1837
+ failed++;
1838
+ }
1839
+ }
1840
+ if (reactEntry) {
1841
+ const fileName = basename4(reactEntry);
1842
+ try {
1843
+ console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
1844
+ const result = await deployReact(reactEntry, privateKey, config.api);
1845
+ if (result.skipped) {
1846
+ console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1847
+ skipped++;
1848
+ } else {
1849
+ console.log(chalk3.green("\u2713 Deployed ") + chalk3.bold(fileName));
1850
+ successful++;
1851
+ }
1852
+ } catch (error) {
1853
+ console.error(chalk3.red("\u2717 Failed ") + chalk3.bold(fileName) + chalk3.red(`: ${error instanceof Error ? error.message : error}`));
1854
+ failed++;
1855
+ }
1856
+ }
1857
+ console.log("");
1858
+ if (failed > 0) {
1859
+ console.log(chalk3.red(`Deployment completed with errors: ${successful} deployed, ${skipped} skipped, ${failed} failed`));
1860
+ process.exit(1);
1861
+ } else {
1862
+ console.log(chalk3.green(`Deployment completed: ${successful} deployed, ${skipped} skipped`));
1863
+ }
1864
+ };
1865
+ var registerDeployCommand = (program) => {
1866
+ program.command("deploy").description("Bundle and deploy functions and React SPAs").argument("[file]", "Path to a specific file (deploys all if omitted)").option("-e, --env <name>", "Use a specific config environment").option("-f, --force", "Force deploy even if unchanged").action(async (file, options) => {
1867
+ let config;
1868
+ if (options.env) {
1869
+ config = getConfig(options.env);
1870
+ if (!config) {
1871
+ console.error(chalk3.red(`\u2717 Config "${options.env}" not found.`));
1872
+ console.error(chalk3.gray(' Run "nulljs config list" to see available configs'));
1873
+ process.exit(1);
1874
+ }
1875
+ } else {
1876
+ config = await selectConfig();
1877
+ if (!config) {
1878
+ console.error(chalk3.red("\u2717 No configurations found."));
1879
+ console.error(chalk3.gray(' Run "nulljs dev" first to initialize the project'));
1880
+ process.exit(1);
1881
+ }
1882
+ }
1883
+ if (!file) {
1884
+ await deployAll(config, options);
1885
+ return;
1886
+ }
1887
+ const filePath = resolve2(file);
1888
+ if (!existsSync5(filePath)) {
1889
+ console.error(chalk3.red(`\u2717 File not found: ${filePath}`));
1890
+ process.exit(1);
1891
+ }
1892
+ if (options.force) {
1893
+ clearDeployCache();
1894
+ }
1895
+ const fileName = basename4(filePath);
1896
+ const privateKey = await loadPrivateKey(config);
1897
+ try {
1898
+ if (isReact(filePath)) {
1899
+ console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
1900
+ const result = await deployReact(filePath, privateKey, config.api);
1901
+ if (result.skipped) {
1902
+ console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1903
+ } else {
1904
+ console.log(chalk3.green("\u2713 Deployed ") + chalk3.bold(fileName) + chalk3.gray(` to ${config.name}`));
1905
+ }
1906
+ } else {
1907
+ console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
1908
+ const result = await deployFunction(filePath, privateKey, config.api);
1909
+ if (result.skipped) {
1910
+ console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1911
+ } else {
1912
+ console.log(chalk3.green("\u2713 Deployed ") + chalk3.bold(fileName) + chalk3.gray(` to ${config.name}`));
1913
+ }
1914
+ }
1915
+ } catch (error) {
1916
+ console.error(chalk3.red("\u2717 Deployment failed:"), error instanceof Error ? error.message : error);
1917
+ process.exit(1);
1918
+ }
1919
+ });
1920
+ };
1921
+ // src/commands/config.ts
1922
+ init_config();
1923
+ import { Command } from "commander";
1924
+ import chalk4 from "chalk";
1925
+ import * as p2 from "@clack/prompts";
1926
+ var registerConfigCommand = (program) => {
1927
+ program.command("config").description("Configuration management").addCommand(new Command("new").description("Create a new configuration").argument("[name]", "Configuration name (e.g., prod, staging)").argument("[api]", "API endpoint (e.g., https://api.example.com)").action(async (name, api) => {
1928
+ let configName = name;
1929
+ let configApi = api;
1930
+ if (!configName || !configApi) {
1931
+ p2.intro(chalk4.cyan("Create new configuration"));
1932
+ if (!configName) {
1933
+ const nameInput = await p2.text({
1934
+ message: "Configuration name",
1935
+ placeholder: "e.g., prod, staging",
1936
+ validate: (value) => {
1937
+ if (!value.trim())
1938
+ return "Name is required";
1939
+ if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
1940
+ return "Name can only contain letters, numbers, hyphens and underscores";
1941
+ }
1942
+ }
1943
+ });
1944
+ if (p2.isCancel(nameInput)) {
1945
+ p2.cancel("Configuration cancelled");
1946
+ process.exit(0);
1947
+ }
1948
+ configName = nameInput;
1949
+ }
1950
+ if (!configApi) {
1951
+ const apiInput = await p2.text({
1952
+ message: "API endpoint",
1953
+ placeholder: "e.g., https://api.example.com",
1954
+ validate: (value) => {
1955
+ if (!value.trim())
1956
+ return "API endpoint is required";
1957
+ try {
1958
+ new URL(value);
1959
+ } catch {
1960
+ return "Please enter a valid URL";
1961
+ }
1962
+ }
1963
+ });
1964
+ if (p2.isCancel(apiInput)) {
1965
+ p2.cancel("Configuration cancelled");
1966
+ process.exit(0);
1967
+ }
1968
+ configApi = apiInput;
1969
+ }
1970
+ }
1971
+ await createConfig(configName, configApi);
1972
+ })).addCommand(new Command("list").description("List all configurations").action(() => {
1973
+ const result = listConfigs();
1974
+ if (!result || result.configs.length === 0) {
1975
+ console.log(chalk4.yellow("No configurations found."));
1976
+ console.log(chalk4.gray(' Run "nulljs dev" to create a dev config, or'));
1977
+ console.log(chalk4.gray(' Run "nulljs config new <name> <api>" to create a new config'));
1978
+ return;
1979
+ }
1980
+ console.log(chalk4.bold("Configurations:"));
1981
+ result.configs.forEach((config) => {
1982
+ const isActive = config.name === result.current;
1983
+ const marker = isActive ? chalk4.green("\u25CF") : chalk4.gray("\u25CB");
1984
+ const name = isActive ? chalk4.green(config.name) : config.name;
1985
+ const suffix = isActive ? chalk4.green(" (active)") : "";
1986
+ console.log(` ${marker} ${name}${suffix}`);
1987
+ console.log(chalk4.gray(` API: ${config.api}`));
1988
+ });
1989
+ })).addCommand(new Command("use").description("Switch to a configuration").argument("[name]", "Configuration name").action(async (name) => {
1990
+ let configName = name;
1991
+ if (!configName) {
1992
+ const configList = listConfigs();
1993
+ if (!configList || configList.configs.length === 0) {
1994
+ console.log(chalk4.yellow("No configurations found."));
1995
+ console.log(chalk4.gray(' Run "nulljs dev" to create a dev config, or'));
1996
+ console.log(chalk4.gray(' Run "nulljs config new" to create a new config'));
1997
+ return;
1998
+ }
1999
+ const selected = await p2.select({
2000
+ message: "Select config to use",
2001
+ options: configList.configs.map((c) => ({
2002
+ value: c.name,
2003
+ label: c.name,
2004
+ hint: c.name === configList.current ? "current" : c.api
2005
+ })),
2006
+ initialValue: configList.current
2007
+ });
2008
+ if (p2.isCancel(selected)) {
2009
+ p2.cancel("Cancelled");
2010
+ process.exit(0);
2011
+ }
2012
+ configName = selected;
2013
+ }
2014
+ useConfig(configName);
2015
+ }));
2016
+ };
2017
+ // src/commands/status.ts
2018
+ init_config();
2019
+ import chalk5 from "chalk";
2020
+ var registerStatusCommand = (program) => {
2021
+ program.action(() => {
2022
+ const result = listConfigs();
2023
+ if (result && result.configs.length > 0) {
2024
+ const currentConfig = result.configs.find((c) => c.name === result.current);
2025
+ if (currentConfig) {
2026
+ console.log(chalk5.green("\u25CF") + " " + chalk5.bold(`Active: ${currentConfig.name}`));
2027
+ console.log(chalk5.blue(" API:") + ` ${currentConfig.api}`);
2028
+ console.log(chalk5.blue(" Public Key:") + ` ${currentConfig.key.public.substring(0, 20)}...`);
2029
+ if (result.configs.length > 1) {
2030
+ console.log(chalk5.gray(`
2031
+ ${result.configs.length - 1} other config(s) available`));
2032
+ }
2033
+ }
2034
+ } else {
2035
+ console.log(chalk5.yellow("\u25CB No local configuration found."));
2036
+ console.log(chalk5.gray(' Run "nulljs dev" to initialize the project'));
2037
+ }
2038
+ printAvailableCommands();
2039
+ });
2040
+ };
2041
+ var printAvailableCommands = () => {
2042
+ console.log(`
2043
+ ` + chalk5.bold("Commands:"));
2044
+ console.log(chalk5.cyan(" nulljs dev") + chalk5.gray(" - Launch development server"));
2045
+ console.log(chalk5.cyan(" nulljs deploy") + chalk5.gray(" - Deploy all functions and React apps"));
2046
+ console.log(chalk5.cyan(" nulljs config list") + chalk5.gray(" - List all configurations"));
2047
+ console.log(chalk5.cyan(" nulljs config new") + chalk5.gray(" - Create new configuration"));
2048
+ console.log(chalk5.cyan(" nulljs config use") + chalk5.gray(" - Switch active configuration"));
2049
+ console.log(chalk5.cyan(" nulljs secret list") + chalk5.gray(" - List all secret keys"));
2050
+ console.log(chalk5.cyan(" nulljs secret create") + chalk5.gray(" - Create a new secret"));
2051
+ console.log(chalk5.cyan(" nulljs secret deploy") + chalk5.gray(" - Deploy secrets from .secret file"));
2052
+ console.log(chalk5.cyan(" nulljs host") + chalk5.gray(" - Set up production hosting with systemd"));
2053
+ };
2054
+ // src/commands/secret.ts
2055
+ init_api();
2056
+ init_config();
2057
+ import { Command as Command2 } from "commander";
2058
+ import chalk6 from "chalk";
2059
+ import { resolve as resolve3 } from "path";
2060
+ import { readFile, readdir as readdir3 } from "fs/promises";
2061
+ import { existsSync as existsSync6 } from "fs";
2062
+ import * as p3 from "@clack/prompts";
2063
+ var selectConfig2 = async () => {
2064
+ const configList = listConfigs();
2065
+ if (!configList || configList.configs.length === 0) {
2066
+ return null;
2067
+ }
2068
+ if (configList.configs.length === 1) {
2069
+ return configList.configs[0] ?? null;
2070
+ }
2071
+ const selected = await p3.select({
2072
+ message: "Select config",
2073
+ options: configList.configs.map((c) => ({
2074
+ value: c.name,
2075
+ label: c.name,
2076
+ hint: c.name === configList.current ? "current" : c.api
2077
+ })),
2078
+ initialValue: configList.current
2079
+ });
2080
+ if (p3.isCancel(selected)) {
2081
+ p3.cancel("Cancelled");
2082
+ process.exit(0);
2083
+ }
2084
+ return getConfig(selected);
2085
+ };
2086
+ var selectFile = async (cwd) => {
2087
+ const files = [];
2088
+ try {
2089
+ const items = await readdir3(cwd);
2090
+ for (const item of items) {
2091
+ if (item === ".secret" || item.startsWith(".secret.")) {
2092
+ files.push(item);
2093
+ }
2094
+ }
2095
+ } catch {}
2096
+ if (files.length === 0) {
2097
+ console.log(chalk6.yellow("No .secret files found in current directory"));
2098
+ return null;
2099
+ }
2100
+ const selected = await p3.select({
2101
+ message: "Select file to import secrets from",
2102
+ options: files.map((f) => ({
2103
+ value: f,
2104
+ label: f
2105
+ }))
2106
+ });
2107
+ if (p3.isCancel(selected)) {
2108
+ p3.cancel("Cancelled");
2109
+ process.exit(0);
2110
+ }
2111
+ return resolve3(cwd, selected);
2112
+ };
2113
+ var parseEnvFile = async (filePath) => {
2114
+ const content = await readFile(filePath, "utf-8");
2115
+ const lines = content.split(`
2116
+ `);
2117
+ const secrets2 = [];
2118
+ for (const line of lines) {
2119
+ const trimmed = line.trim();
2120
+ if (!trimmed || trimmed.startsWith("#")) {
2121
+ continue;
2122
+ }
2123
+ const equalIndex = trimmed.indexOf("=");
2124
+ if (equalIndex === -1) {
2125
+ continue;
2126
+ }
2127
+ const key = trimmed.substring(0, equalIndex).trim();
2128
+ let value = trimmed.substring(equalIndex + 1).trim();
2129
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2130
+ value = value.slice(1, -1);
2131
+ }
2132
+ if (key) {
2133
+ secrets2.push({ key, value });
2134
+ }
2135
+ }
2136
+ return secrets2;
2137
+ };
2138
+ var registerSecretCommand = (program) => {
2139
+ program.command("secret").description("Secret management").addCommand(new Command2("list").description("List all secret keys").option("-e, --env <name>", "Use a specific config environment").action(async (options) => {
2140
+ let config;
2141
+ try {
2142
+ config = options.env ? getConfig(options.env) : await selectConfig2();
2143
+ } catch (err) {
2144
+ console.error(chalk6.red("\u2717 Failed to get config:"), err);
2145
+ process.exit(1);
2146
+ }
2147
+ await new Promise((resolve4) => setImmediate(resolve4));
2148
+ if (!config) {
2149
+ console.error(chalk6.red("\u2717 No configuration found."));
2150
+ console.error(chalk6.gray(' Run "nulljs dev" first to initialize the project'));
2151
+ process.exit(1);
2152
+ }
2153
+ try {
2154
+ console.log(chalk6.gray(`Fetching secrets from ${config.api}...`));
2155
+ const privateKey = await loadPrivateKey(config);
2156
+ const keys = await listSecrets({ privateKey, url: config.api });
2157
+ if (keys.length === 0) {
2158
+ console.log(chalk6.yellow("No secrets found"));
2159
+ return;
2160
+ }
2161
+ console.log(chalk6.bold(`
2162
+ Secret keys:`));
2163
+ keys.forEach((key, index) => {
2164
+ console.log(chalk6.green(` ${index + 1}.`) + ` ${chalk6.cyan(key)}`);
2165
+ });
2166
+ } catch (error) {
2167
+ console.error(chalk6.red("\u2717 Failed to list secrets:"), error instanceof Error ? error.message : String(error));
2168
+ process.exit(1);
2169
+ }
2170
+ })).addCommand(new Command2("create").description("Create a new secret").argument("[key]", "Secret key").argument("[value]", "Secret value").option("-e, --env <name>", "Use a specific config environment").action(async (key, value, options) => {
2171
+ const config = options?.env ? getConfig(options.env) : await selectConfig2();
2172
+ if (!config) {
2173
+ console.error(chalk6.red("\u2717 No configuration found."));
2174
+ console.error(chalk6.gray(' Run "nulljs dev" first to initialize the project'));
2175
+ process.exit(1);
2176
+ }
2177
+ let secretKey = key;
2178
+ let secretValue = value;
2179
+ if (!secretKey || !secretValue) {
2180
+ if (!secretKey) {
2181
+ const keyInput = await p3.text({
2182
+ message: "Secret key",
2183
+ placeholder: "e.g., API_KEY, DATABASE_URL",
2184
+ validate: (v) => {
2185
+ if (!v.trim())
2186
+ return "Key is required";
2187
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v)) {
2188
+ return "Key must start with a letter or underscore and contain only letters, numbers, and underscores";
2189
+ }
2190
+ }
2191
+ });
2192
+ if (p3.isCancel(keyInput)) {
2193
+ p3.cancel("Cancelled");
2194
+ process.exit(0);
2195
+ }
2196
+ secretKey = keyInput;
2197
+ }
2198
+ if (!secretValue) {
2199
+ const valueInput = await p3.password({
2200
+ message: "Secret value",
2201
+ validate: (v) => {
2202
+ if (!v)
2203
+ return "Value is required";
2204
+ }
2205
+ });
2206
+ if (p3.isCancel(valueInput)) {
2207
+ p3.cancel("Cancelled");
2208
+ process.exit(0);
2209
+ }
2210
+ secretValue = valueInput;
2211
+ }
2212
+ }
2213
+ await new Promise((resolve4) => setImmediate(resolve4));
2214
+ try {
2215
+ const privateKey = await loadPrivateKey(config);
2216
+ await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api });
2217
+ console.log(chalk6.green("\u2713 Secret created:") + ` ${chalk6.cyan(secretKey)}`);
2218
+ } catch (error) {
2219
+ console.error(chalk6.red("\u2717 Failed to create secret:"), error instanceof Error ? error.message : error);
2220
+ process.exit(1);
2221
+ }
2222
+ })).addCommand(new Command2("deploy").description("Deploy secrets from a .secret file").argument("[file]", "Path to .secret file").option("-e, --env <name>", "Use a specific config environment").action(async (file, options) => {
2223
+ const config = options?.env ? getConfig(options.env) : await selectConfig2();
2224
+ if (!config) {
2225
+ console.error(chalk6.red("\u2717 No configuration found."));
2226
+ console.error(chalk6.gray(' Run "nulljs dev" first to initialize the project'));
2227
+ process.exit(1);
2228
+ }
2229
+ let filePath = file ? resolve3(file) : null;
2230
+ if (!filePath) {
2231
+ filePath = await selectFile(process.cwd());
2232
+ if (!filePath) {
2233
+ process.exit(1);
2234
+ }
2235
+ }
2236
+ if (!existsSync6(filePath)) {
2237
+ console.error(chalk6.red(`\u2717 File not found: ${filePath}`));
2238
+ process.exit(1);
2239
+ }
2240
+ await new Promise((resolve4) => setImmediate(resolve4));
2241
+ try {
2242
+ const secrets2 = await parseEnvFile(filePath);
2243
+ if (secrets2.length === 0) {
2244
+ console.log(chalk6.yellow("No secrets found in file"));
2245
+ return;
2246
+ }
2247
+ const privateKey = await loadPrivateKey(config);
2248
+ let created = 0;
2249
+ let failed = 0;
2250
+ console.log(chalk6.cyan(`Deploying ${secrets2.length} secret(s) to ${config.name}...
2251
+ `));
2252
+ for (const secret of secrets2) {
2253
+ try {
2254
+ await createSecret(secret, { privateKey, url: config.api });
2255
+ console.log(chalk6.green("\u2713") + ` ${secret.key}`);
2256
+ created++;
2257
+ } catch (error) {
2258
+ console.log(chalk6.red("\u2717") + ` ${secret.key}: ${error instanceof Error ? error.message : error}`);
2259
+ failed++;
2260
+ }
2261
+ }
2262
+ console.log("");
2263
+ if (failed > 0) {
2264
+ console.log(chalk6.yellow(`Deploy completed: ${created} created, ${failed} failed`));
2265
+ } else {
2266
+ console.log(chalk6.green(`Deploy completed: ${created} secret(s) created`));
2267
+ }
2268
+ } catch (error) {
2269
+ console.error(chalk6.red("\u2717 Failed to deploy secrets:"), error instanceof Error ? error.message : error);
2270
+ process.exit(1);
2271
+ }
2272
+ })).addCommand(new Command2("import").description("Import secrets from a file (alias for deploy)").argument("[file]", "Path to .secret file").option("-e, --env <name>", "Use a specific config environment").action(async (file, options) => {
2273
+ const config = options?.env ? getConfig(options.env) : await selectConfig2();
2274
+ if (!config) {
2275
+ console.error(chalk6.red("\u2717 No configuration found."));
2276
+ console.error(chalk6.gray(' Run "nulljs dev" first to initialize the project'));
2277
+ process.exit(1);
2278
+ }
2279
+ let filePath = file ? resolve3(file) : null;
2280
+ if (!filePath) {
2281
+ filePath = await selectFile(process.cwd());
2282
+ if (!filePath) {
2283
+ process.exit(1);
2284
+ }
2285
+ }
2286
+ if (!existsSync6(filePath)) {
2287
+ console.error(chalk6.red(`\u2717 File not found: ${filePath}`));
2288
+ process.exit(1);
2289
+ }
2290
+ await new Promise((resolve4) => setImmediate(resolve4));
2291
+ try {
2292
+ const secrets2 = await parseEnvFile(filePath);
2293
+ if (secrets2.length === 0) {
2294
+ console.log(chalk6.yellow("No secrets found in file"));
2295
+ return;
2296
+ }
2297
+ const privateKey = await loadPrivateKey(config);
2298
+ let created = 0;
2299
+ let failed = 0;
2300
+ console.log(chalk6.cyan(`Importing ${secrets2.length} secret(s) to ${config.name}...
2301
+ `));
2302
+ for (const secret of secrets2) {
2303
+ try {
2304
+ await createSecret(secret, { privateKey, url: config.api });
2305
+ console.log(chalk6.green("\u2713") + ` ${secret.key}`);
2306
+ created++;
2307
+ } catch (error) {
2308
+ console.log(chalk6.red("\u2717") + ` ${secret.key}: ${error instanceof Error ? error.message : error}`);
2309
+ failed++;
2310
+ }
2311
+ }
2312
+ console.log("");
2313
+ if (failed > 0) {
2314
+ console.log(chalk6.yellow(`Import completed: ${created} created, ${failed} failed`));
2315
+ } else {
2316
+ console.log(chalk6.green(`Import completed: ${created} secret(s) created`));
2317
+ }
2318
+ } catch (error) {
2319
+ console.error(chalk6.red("\u2717 Failed to import secrets:"), error instanceof Error ? error.message : error);
2320
+ process.exit(1);
2321
+ }
2322
+ }));
2323
+ };
2324
+ // src/commands/host.ts
2325
+ import { existsSync as existsSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2326
+ import { join as join5 } from "path";
2327
+ import { execSync } from "child_process";
2328
+ import { homedir } from "os";
2329
+ import chalk7 from "chalk";
2330
+ var PLATFORMS2 = {
2331
+ "linux-x64": "@tothalex/nulljs-linux-x64",
2332
+ "linux-arm64": "@tothalex/nulljs-linux-arm64",
2333
+ "darwin-arm64": "@tothalex/nulljs-darwin-arm64"
2334
+ };
2335
+ var getPlatformKey2 = () => {
2336
+ return `${process.platform}-${process.arch}`;
2337
+ };
2338
+ var getServerBinPath = () => {
2339
+ const platformKey = getPlatformKey2();
2340
+ const pkgName = PLATFORMS2[platformKey];
2341
+ if (!pkgName) {
2342
+ throw new Error(`Unsupported platform: ${platformKey}`);
2343
+ }
2344
+ return __require.resolve(`${pkgName}/bin/server`);
2345
+ };
2346
+ var generateProductionKeys = async () => {
2347
+ console.log(chalk7.blue("Generating production keys..."));
2348
+ const keyPair = await crypto.subtle.generateKey({
2349
+ name: "Ed25519",
2350
+ namedCurve: "Ed25519"
2351
+ }, true, ["sign", "verify"]);
2352
+ const privateKeyBuffer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
2353
+ const publicKeyBuffer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
2354
+ const privateKey = btoa(String.fromCharCode(...new Uint8Array(privateKeyBuffer)));
2355
+ const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)));
2356
+ return { privateKey, publicKey };
2357
+ };
2358
+ var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
2359
+ const configPath = join5(cloudPath, "config.json");
2360
+ const config = {
2361
+ key: {
2362
+ private: privateKey,
2363
+ public: publicKey
2364
+ }
2365
+ };
2366
+ console.log(chalk7.blue("Saving production configuration..."));
2367
+ writeFileSync2(configPath, JSON.stringify(config, null, 2));
2368
+ execSync(`chmod 600 ${configPath}`, { stdio: "inherit" });
2369
+ console.log(chalk7.green("Production configuration saved"));
2370
+ };
2371
+ var createSystemdService = (cloudPath, publicKey, serverBinPath, userName) => {
2372
+ const serviceContent = `[Unit]
2373
+ Description=NullJS Server
2374
+ After=network.target
2375
+ StartLimitIntervalSec=0
2376
+
2377
+ [Service]
2378
+ Type=simple
2379
+ Restart=always
2380
+ RestartSec=1
2381
+ User=${userName}
2382
+ ExecStart=${serverBinPath}
2383
+ Environment=CLOUD_PATH=${cloudPath}
2384
+ Environment=PUBLIC_KEY=${publicKey}
2385
+ WorkingDirectory=${cloudPath}
2386
+
2387
+ [Install]
2388
+ WantedBy=multi-user.target
2389
+ `;
2390
+ return serviceContent;
2391
+ };
2392
+ var ensureCloudDirectory = (cloudPath) => {
2393
+ if (!existsSync7(cloudPath)) {
2394
+ console.log(chalk7.blue("Creating cloud directory..."));
2395
+ try {
2396
+ mkdirSync3(cloudPath, { recursive: true });
2397
+ console.log(chalk7.green(`Cloud directory created: ${cloudPath}`));
2398
+ } catch (error) {
2399
+ console.log(chalk7.red("Failed to create cloud directory:"), error instanceof Error ? error.message : String(error));
2400
+ throw error;
2401
+ }
2402
+ } else {
2403
+ console.log(chalk7.gray(`Cloud directory already exists: ${cloudPath}`));
2404
+ }
2405
+ };
2406
+ var installSystemdService = (serviceContent) => {
2407
+ const servicePath = "/etc/systemd/system/nulljs.service";
2408
+ console.log(chalk7.blue("Installing systemd service..."));
2409
+ writeFileSync2("/tmp/nulljs.service", serviceContent);
2410
+ execSync(`sudo mv /tmp/nulljs.service ${servicePath}`, { stdio: "inherit" });
2411
+ execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
2412
+ execSync("sudo systemctl enable nulljs.service", { stdio: "inherit" });
2413
+ console.log(chalk7.green("Systemd service installed and enabled"));
2414
+ };
2415
+ var validateLinux = () => {
2416
+ if (process.platform !== "linux") {
2417
+ console.log(chalk7.red("This command only works on Linux"));
2418
+ process.exit(1);
2419
+ }
2420
+ };
2421
+ var validateServerBinary = (serverBinPath) => {
2422
+ if (!existsSync7(serverBinPath)) {
2423
+ console.log(chalk7.red("Server binary not found at:"), serverBinPath);
2424
+ console.log(chalk7.yellow("Make sure you have installed the package correctly"));
2425
+ process.exit(1);
2426
+ }
2427
+ };
2428
+ var checkExistingProductionConfig = (cloudPath) => {
2429
+ const configPath = join5(cloudPath, "config.json");
2430
+ if (!existsSync7(configPath)) {
2431
+ return null;
2432
+ }
2433
+ try {
2434
+ const configContent = JSON.parse(readFileSync2(configPath, "utf8"));
2435
+ if (configContent.key?.private && configContent.key?.public) {
2436
+ return configContent;
2437
+ }
2438
+ } catch {
2439
+ console.log(chalk7.yellow("Existing production config is invalid, will regenerate"));
2440
+ }
2441
+ return null;
2442
+ };
2443
+ var unhost = async (options = {}) => {
2444
+ validateLinux();
2445
+ console.log(chalk7.blue("Removing NullJS production hosting..."));
2446
+ try {
2447
+ console.log(chalk7.blue("Stopping NullJS service..."));
2448
+ try {
2449
+ execSync("sudo systemctl stop nulljs", { stdio: "inherit" });
2450
+ console.log(chalk7.green("Service stopped"));
2451
+ } catch {
2452
+ console.log(chalk7.yellow("Service was not running"));
2453
+ }
2454
+ try {
2455
+ execSync("sudo systemctl disable nulljs", { stdio: "inherit" });
2456
+ console.log(chalk7.green("Service disabled"));
2457
+ } catch {
2458
+ console.log(chalk7.yellow("Service was not enabled"));
2459
+ }
2460
+ const servicePath = "/etc/systemd/system/nulljs.service";
2461
+ if (existsSync7(servicePath)) {
2462
+ console.log(chalk7.blue("Removing systemd service file..."));
2463
+ execSync(`sudo rm ${servicePath}`, { stdio: "inherit" });
2464
+ execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
2465
+ console.log(chalk7.green("Systemd service file removed"));
2466
+ }
2467
+ if (!options.keepData) {
2468
+ const defaultCloudPath = join5(homedir(), ".nulljs");
2469
+ if (existsSync7(defaultCloudPath)) {
2470
+ console.log(chalk7.blue("Removing cloud directory..."));
2471
+ execSync(`rm -rf ${defaultCloudPath}`, { stdio: "inherit" });
2472
+ console.log(chalk7.green("Cloud directory removed"));
2473
+ } else {
2474
+ console.log(chalk7.gray("Cloud directory does not exist"));
2475
+ }
2476
+ } else {
2477
+ console.log(chalk7.gray("Keeping cloud data (--keep-data specified)"));
2478
+ }
2479
+ console.log(chalk7.green("NullJS hosting cleanup complete!"));
2480
+ console.log("");
2481
+ console.log(chalk7.blue("Summary:"));
2482
+ console.log(chalk7.gray(" - Systemd service stopped and removed"));
2483
+ if (!options.keepData) {
2484
+ console.log(chalk7.gray(" - Cloud directory removed"));
2485
+ }
2486
+ } catch (error) {
2487
+ console.log(chalk7.red("Failed to remove hosting setup:"), error instanceof Error ? error.message : String(error));
2488
+ process.exit(1);
2489
+ }
2490
+ };
2491
+ var host = async (cloudPath) => {
2492
+ validateLinux();
2493
+ const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath : join5(process.cwd(), cloudPath) : join5(homedir(), ".nulljs");
2494
+ console.log(chalk7.blue("Setting up NullJS production hosting..."));
2495
+ console.log(chalk7.gray(` Cloud path: ${resolvedCloudPath}`));
2496
+ const serverBinPath = getServerBinPath();
2497
+ validateServerBinary(serverBinPath);
2498
+ const currentUser = process.env.USER || process.env.USERNAME || "root";
2499
+ console.log(chalk7.blue(`Setting up service to run as user: ${currentUser}`));
2500
+ ensureCloudDirectory(resolvedCloudPath);
2501
+ let productionConfig = checkExistingProductionConfig(resolvedCloudPath);
2502
+ if (!productionConfig) {
2503
+ const { privateKey, publicKey } = await generateProductionKeys();
2504
+ saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
2505
+ productionConfig = { key: { private: privateKey, public: publicKey } };
2506
+ console.log(chalk7.green("Production keys generated"));
2507
+ console.log(chalk7.blue("Production Public Key:"));
2508
+ console.log(chalk7.gray(productionConfig.key.public));
2509
+ } else {
2510
+ console.log(chalk7.green("Using existing production keys"));
2511
+ console.log(chalk7.blue("Production Public Key:"));
2512
+ console.log(chalk7.gray(productionConfig.key.public));
2513
+ }
2514
+ const serviceContent = createSystemdService(resolvedCloudPath, productionConfig.key.public, serverBinPath, currentUser);
2515
+ installSystemdService(serviceContent);
2516
+ console.log(chalk7.green("NullJS hosting setup complete!"));
2517
+ console.log("");
2518
+ console.log(chalk7.blue("Service management commands:"));
2519
+ console.log(chalk7.gray(" Start: "), chalk7.white("sudo systemctl start nulljs"));
2520
+ console.log(chalk7.gray(" Stop: "), chalk7.white("sudo systemctl stop nulljs"));
2521
+ console.log(chalk7.gray(" Status: "), chalk7.white("sudo systemctl status nulljs"));
2522
+ console.log(chalk7.gray(" Logs: "), chalk7.white("sudo journalctl -u nulljs -f"));
2523
+ console.log("");
2524
+ console.log(chalk7.yellow("Remember to start the service: sudo systemctl start nulljs"));
2525
+ };
2526
+ var registerHostCommand = (program) => {
2527
+ const hostCmd = program.command("host").description("Set up NullJS production hosting with systemd").argument("[cloud-path]", "Path for cloud storage (default: ~/.nulljs)").action(async (cloudPath) => {
2528
+ await host(cloudPath);
2529
+ });
2530
+ hostCmd.command("remove").description("Remove NullJS production hosting").option("--keep-data", "Keep cloud data directory").action(async (options) => {
2531
+ await unhost(options);
2532
+ });
2533
+ hostCmd.command("logs").description("View NullJS server logs").option("-n, --lines <number>", "Number of lines to show", "100").option("--no-follow", "Do not follow log output").action((options) => {
2534
+ validateLinux();
2535
+ const args = ["-u", "nulljs", "-n", options.lines];
2536
+ if (options.follow) {
2537
+ args.push("-f");
2538
+ }
2539
+ try {
2540
+ execSync(`journalctl ${args.join(" ")}`, { stdio: "inherit" });
2541
+ } catch {}
2542
+ });
2543
+ };
2544
+ // src/cli.ts
2545
+ var program = new Command3;
2546
+ program.name("nulljs").description("NullJS CLI").version("0.0.49");
2547
+ registerDevCommand(program);
2548
+ registerDeployCommand(program);
2549
+ registerConfigCommand(program);
2550
+ registerStatusCommand(program);
2551
+ registerSecretCommand(program);
2552
+ registerHostCommand(program);
2553
+ program.parse();