@tothalex/nulljs 0.0.58 → 0.0.60
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.
- package/dist/cli.js +351 -202
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -52,69 +52,6 @@ var buildUrl = (path, options) => {
|
|
|
52
52
|
// ../../packages/api/actions/deployments.ts
|
|
53
53
|
var init_deployments = () => {};
|
|
54
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
55
|
// ../../packages/api/actions/invocations.ts
|
|
119
56
|
var init_invocations = () => {};
|
|
120
57
|
|
|
@@ -134,64 +71,6 @@ var init_assets = () => {};
|
|
|
134
71
|
// ../../packages/api/actions/activity.ts
|
|
135
72
|
var init_activity = () => {};
|
|
136
73
|
|
|
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
74
|
// ../../packages/api/actions/index.ts
|
|
196
75
|
var init_actions = __esm(() => {
|
|
197
76
|
init_deployments();
|
|
@@ -635,6 +514,183 @@ var init_bundle = __esm(() => {
|
|
|
635
514
|
init_function();
|
|
636
515
|
});
|
|
637
516
|
|
|
517
|
+
// ../../packages/api/actions/http.ts
|
|
518
|
+
import { mkdtempSync, writeFileSync as writeFileSync2, rmSync } from "fs";
|
|
519
|
+
import { tmpdir } from "os";
|
|
520
|
+
import { join as join3 } from "path";
|
|
521
|
+
var {$ } = globalThis.Bun;
|
|
522
|
+
var parseCurlResponse = (result) => {
|
|
523
|
+
const lines = result.trim().split(`
|
|
524
|
+
`);
|
|
525
|
+
const status = parseInt(lines.pop() || "0", 10);
|
|
526
|
+
const body = lines.join(`
|
|
527
|
+
`);
|
|
528
|
+
return {
|
|
529
|
+
ok: status >= 200 && status < 300,
|
|
530
|
+
status,
|
|
531
|
+
body
|
|
532
|
+
};
|
|
533
|
+
}, buildHeaderFlags = (headers) => {
|
|
534
|
+
const flags = [];
|
|
535
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
536
|
+
flags.push("-H");
|
|
537
|
+
flags.push(`${key}: ${value}`);
|
|
538
|
+
}
|
|
539
|
+
return flags;
|
|
540
|
+
}, httpGet = async (path, options) => {
|
|
541
|
+
const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
|
|
542
|
+
const headers = options?.headers || {};
|
|
543
|
+
if (options?.useCurl) {
|
|
544
|
+
const headerFlags = buildHeaderFlags(headers);
|
|
545
|
+
const result = await $`curl -s -w '\n%{http_code}' -X GET ${headerFlags} ${url}`.text();
|
|
546
|
+
return parseCurlResponse(result);
|
|
547
|
+
}
|
|
548
|
+
const response = await fetch(url, {
|
|
549
|
+
method: "GET",
|
|
550
|
+
headers
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
ok: response.ok,
|
|
554
|
+
status: response.status,
|
|
555
|
+
body: await response.text()
|
|
556
|
+
};
|
|
557
|
+
}, httpPost = async (path, body, options) => {
|
|
558
|
+
const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
|
|
559
|
+
const headers = options?.headers || {};
|
|
560
|
+
if (options?.useCurl) {
|
|
561
|
+
const tempDir = mkdtempSync(join3(tmpdir(), "nulljs-http-"));
|
|
562
|
+
const bodyFile = join3(tempDir, "body.json");
|
|
563
|
+
try {
|
|
564
|
+
writeFileSync2(bodyFile, body);
|
|
565
|
+
const headerFlags = buildHeaderFlags(headers);
|
|
566
|
+
const result = await $`curl -s -w '\n%{http_code}' -X POST ${headerFlags} -d @${bodyFile} ${url}`.text();
|
|
567
|
+
return parseCurlResponse(result);
|
|
568
|
+
} finally {
|
|
569
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const response = await fetch(url, {
|
|
573
|
+
method: "POST",
|
|
574
|
+
headers,
|
|
575
|
+
body
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
ok: response.ok,
|
|
579
|
+
status: response.status,
|
|
580
|
+
body: await response.text()
|
|
581
|
+
};
|
|
582
|
+
}, httpPostFormData = async (path, files, options) => {
|
|
583
|
+
const url = options?.baseUrl ? `${options.baseUrl}${path}` : path;
|
|
584
|
+
const headers = options?.headers || {};
|
|
585
|
+
if (options?.useCurl) {
|
|
586
|
+
const tempDir = mkdtempSync(join3(tmpdir(), "nulljs-http-"));
|
|
587
|
+
try {
|
|
588
|
+
const formParts = [];
|
|
589
|
+
for (const file of files) {
|
|
590
|
+
const filePath = join3(tempDir, file.fileName);
|
|
591
|
+
writeFileSync2(filePath, file.content);
|
|
592
|
+
formParts.push("-F");
|
|
593
|
+
formParts.push(`${file.fileName}=@${filePath};type=${file.mimeType}`);
|
|
594
|
+
}
|
|
595
|
+
const headerFlags = buildHeaderFlags(headers);
|
|
596
|
+
const result = await $`curl -s -w '\n%{http_code}' -X POST ${headerFlags} ${formParts} ${url}`.text();
|
|
597
|
+
return parseCurlResponse(result);
|
|
598
|
+
} finally {
|
|
599
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const form = new FormData;
|
|
603
|
+
for (const file of files) {
|
|
604
|
+
const buffer = Buffer.from(file.content);
|
|
605
|
+
const blob = new Blob([buffer], { type: file.mimeType });
|
|
606
|
+
form.append(file.fileName, blob);
|
|
607
|
+
}
|
|
608
|
+
const response = await fetch(url, {
|
|
609
|
+
method: "POST",
|
|
610
|
+
headers,
|
|
611
|
+
body: form
|
|
612
|
+
});
|
|
613
|
+
return {
|
|
614
|
+
ok: response.ok,
|
|
615
|
+
status: response.status,
|
|
616
|
+
body: await response.text()
|
|
617
|
+
};
|
|
618
|
+
};
|
|
619
|
+
var init_http = () => {};
|
|
620
|
+
|
|
621
|
+
// ../../packages/api/actions/deploy.ts
|
|
622
|
+
var getMimeType = (fileName) => {
|
|
623
|
+
if (fileName.endsWith(".js"))
|
|
624
|
+
return "application/javascript";
|
|
625
|
+
if (fileName.endsWith(".css"))
|
|
626
|
+
return "text/css";
|
|
627
|
+
if (fileName.endsWith(".html"))
|
|
628
|
+
return "text/html";
|
|
629
|
+
if (fileName.endsWith(".json"))
|
|
630
|
+
return "application/json";
|
|
631
|
+
return "application/octet-stream";
|
|
632
|
+
}, assetsToFileData = (assets3) => {
|
|
633
|
+
const files = [];
|
|
634
|
+
const blobs = [];
|
|
635
|
+
for (const asset of assets3) {
|
|
636
|
+
const mimeType = getMimeType(asset.fileName);
|
|
637
|
+
const buffer = Buffer.from(asset.code);
|
|
638
|
+
files.push({
|
|
639
|
+
fileName: asset.fileName,
|
|
640
|
+
content: asset.code,
|
|
641
|
+
mimeType
|
|
642
|
+
});
|
|
643
|
+
blobs.push({
|
|
644
|
+
fileName: asset.fileName,
|
|
645
|
+
size: buffer.length,
|
|
646
|
+
type: mimeType
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return { files, blobs };
|
|
650
|
+
}, createSignature = async (blobs, privateKey) => {
|
|
651
|
+
const sortedBlobs = [...blobs].sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
652
|
+
const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
|
|
653
|
+
const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
|
|
654
|
+
return btoa(String.fromCharCode(...new Uint8Array(sign)));
|
|
655
|
+
}, createFunctionDeployment = async (deployment2, options) => {
|
|
656
|
+
const handler = deployment2.assets.find((asset) => asset.fileName === "handler.js");
|
|
657
|
+
if (!handler) {
|
|
658
|
+
throw new Error(`Handler not found for ${deployment2.name}`);
|
|
659
|
+
}
|
|
660
|
+
const { files, blobs } = assetsToFileData([handler]);
|
|
661
|
+
const signature = await createSignature(blobs, options.privateKey);
|
|
662
|
+
const baseUrl = options.url || "";
|
|
663
|
+
const path = "/api/deployment";
|
|
664
|
+
const result = await httpPostFormData(path, files, {
|
|
665
|
+
baseUrl,
|
|
666
|
+
useCurl: options.useCurl,
|
|
667
|
+
headers: {
|
|
668
|
+
authorization: signature
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
if (!result.ok) {
|
|
672
|
+
throw new Error(`Deployment failed (${result.status}): ${result.body}`);
|
|
673
|
+
}
|
|
674
|
+
}, createReactDeployment = async (deployment2, options) => {
|
|
675
|
+
const { files, blobs } = assetsToFileData(deployment2.assets);
|
|
676
|
+
const signature = await createSignature(blobs, options.privateKey);
|
|
677
|
+
const baseUrl = options.url || "";
|
|
678
|
+
const path = "/api/deployment/react";
|
|
679
|
+
const result = await httpPostFormData(path, files, {
|
|
680
|
+
baseUrl,
|
|
681
|
+
useCurl: options.useCurl,
|
|
682
|
+
headers: {
|
|
683
|
+
authorization: signature
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
if (!result.ok) {
|
|
687
|
+
throw new Error(`Deployment failed (${result.status}): ${result.body}`);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
var init_deploy = __esm(() => {
|
|
691
|
+
init_http();
|
|
692
|
+
});
|
|
693
|
+
|
|
638
694
|
// src/lib/deploy.ts
|
|
639
695
|
var exports_deploy = {};
|
|
640
696
|
__export(exports_deploy, {
|
|
@@ -648,7 +704,7 @@ var deployedHashes, clearDeployCache = () => {
|
|
|
648
704
|
deployedHashes.clear();
|
|
649
705
|
}, hashCode = (code) => {
|
|
650
706
|
return Bun.hash(code).toString(16);
|
|
651
|
-
}, deployFunction = async (filePath, privateKey, apiUrl) => {
|
|
707
|
+
}, deployFunction = async (filePath, privateKey, apiUrl, useCurl) => {
|
|
652
708
|
const fileName = basename2(filePath);
|
|
653
709
|
const result = await build(functionConfig(filePath));
|
|
654
710
|
const handler = result.output.find((output) => output.type === "chunk" && output.fileName === "handler.js");
|
|
@@ -660,10 +716,10 @@ var deployedHashes, clearDeployCache = () => {
|
|
|
660
716
|
if (lastHash === hash) {
|
|
661
717
|
return { deployed: false, skipped: true };
|
|
662
718
|
}
|
|
663
|
-
await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl });
|
|
719
|
+
await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl, useCurl });
|
|
664
720
|
deployedHashes.set(filePath, hash);
|
|
665
721
|
return { deployed: true, skipped: false };
|
|
666
|
-
}, deployReact = async (filePath, privateKey, apiUrl) => {
|
|
722
|
+
}, deployReact = async (filePath, privateKey, apiUrl, useCurl) => {
|
|
667
723
|
const fileName = basename2(filePath);
|
|
668
724
|
const result = await build(spaClientConfig(filePath));
|
|
669
725
|
const assets3 = [];
|
|
@@ -680,12 +736,12 @@ var deployedHashes, clearDeployCache = () => {
|
|
|
680
736
|
if (lastHash === hash) {
|
|
681
737
|
return { deployed: false, skipped: true };
|
|
682
738
|
}
|
|
683
|
-
await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl });
|
|
739
|
+
await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl, useCurl });
|
|
684
740
|
deployedHashes.set(filePath, hash);
|
|
685
741
|
return { deployed: true, skipped: false };
|
|
686
742
|
};
|
|
687
|
-
var
|
|
688
|
-
|
|
743
|
+
var init_deploy2 = __esm(() => {
|
|
744
|
+
init_deploy();
|
|
689
745
|
init_bundle();
|
|
690
746
|
deployedHashes = new Map;
|
|
691
747
|
});
|
|
@@ -693,19 +749,19 @@ var init_deploy = __esm(() => {
|
|
|
693
749
|
// src/lib/watcher.ts
|
|
694
750
|
import { watch, existsSync as existsSync3 } from "fs";
|
|
695
751
|
import { readdir } from "fs/promises";
|
|
696
|
-
import { join as
|
|
752
|
+
import { join as join4 } from "path";
|
|
697
753
|
import { build as build2 } from "vite";
|
|
698
754
|
var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
699
755
|
return Bun.hash(code).toString(16);
|
|
700
756
|
}, findFunctionEntries = async (functionsPath) => {
|
|
701
757
|
const entries = [];
|
|
702
758
|
for (const dir of FUNCTION_DIRS) {
|
|
703
|
-
const dirPath =
|
|
759
|
+
const dirPath = join4(functionsPath, dir);
|
|
704
760
|
try {
|
|
705
761
|
const items = await readdir(dirPath, { withFileTypes: true });
|
|
706
762
|
for (const item of items) {
|
|
707
763
|
if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
|
|
708
|
-
const fullPath =
|
|
764
|
+
const fullPath = join4(dirPath, item.name);
|
|
709
765
|
const name = `${dir}/${item.name.replace(/\.tsx?$/, "")}`;
|
|
710
766
|
entries.push({
|
|
711
767
|
name,
|
|
@@ -725,7 +781,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
|
725
781
|
console.error('No local configuration found. Run "nulljs dev" first.');
|
|
726
782
|
return () => {};
|
|
727
783
|
}
|
|
728
|
-
const functionsPath =
|
|
784
|
+
const functionsPath = join4(srcPath, "function");
|
|
729
785
|
let entries = await findFunctionEntries(functionsPath);
|
|
730
786
|
let viteWatcher = null;
|
|
731
787
|
let isRestarting = false;
|
|
@@ -796,7 +852,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
|
796
852
|
viteWatcher = await startViteWatcher();
|
|
797
853
|
const dirWatchers = [];
|
|
798
854
|
for (const dir of FUNCTION_DIRS) {
|
|
799
|
-
const dirPath =
|
|
855
|
+
const dirPath = join4(functionsPath, dir);
|
|
800
856
|
if (!existsSync3(dirPath))
|
|
801
857
|
continue;
|
|
802
858
|
try {
|
|
@@ -805,7 +861,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
|
805
861
|
return;
|
|
806
862
|
if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
|
|
807
863
|
return;
|
|
808
|
-
const fullPath =
|
|
864
|
+
const fullPath = join4(dirPath, filename);
|
|
809
865
|
const entryName = `${dir}/${filename.replace(/\.tsx?$/, "")}`;
|
|
810
866
|
const existingEntry = entries.find((e) => e.name === entryName);
|
|
811
867
|
const fileExists = existsSync3(fullPath);
|
|
@@ -832,14 +888,14 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
|
832
888
|
}
|
|
833
889
|
const privateKey = await loadPrivateKey(config);
|
|
834
890
|
deployedHashes2.clear();
|
|
835
|
-
const functionsPath =
|
|
891
|
+
const functionsPath = join4(srcPath, "function");
|
|
836
892
|
const entries = await findFunctionEntries(functionsPath);
|
|
837
|
-
const reactEntry =
|
|
893
|
+
const reactEntry = join4(srcPath, "index.tsx");
|
|
838
894
|
const hasReact = existsSync3(reactEntry);
|
|
839
895
|
let total = entries.length + (hasReact ? 1 : 0);
|
|
840
896
|
let successful = 0;
|
|
841
897
|
let failed = 0;
|
|
842
|
-
const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (
|
|
898
|
+
const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (init_deploy2(), exports_deploy));
|
|
843
899
|
clearDeployCache2();
|
|
844
900
|
for (const entry of entries) {
|
|
845
901
|
try {
|
|
@@ -867,7 +923,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
|
867
923
|
var init_watcher = __esm(() => {
|
|
868
924
|
init_config();
|
|
869
925
|
init_bundle();
|
|
870
|
-
|
|
926
|
+
init_deploy();
|
|
871
927
|
FUNCTION_DIRS = ["api", "cron", "event"];
|
|
872
928
|
deployedHashes2 = new Map;
|
|
873
929
|
});
|
|
@@ -1685,9 +1741,9 @@ var registerDevCommand = (program) => {
|
|
|
1685
1741
|
// src/commands/deploy.ts
|
|
1686
1742
|
init_config();
|
|
1687
1743
|
init_bundle();
|
|
1688
|
-
|
|
1744
|
+
init_deploy2();
|
|
1689
1745
|
import chalk3 from "chalk";
|
|
1690
|
-
import { basename as basename4, resolve as resolve2, join as
|
|
1746
|
+
import { basename as basename4, resolve as resolve2, join as join5 } from "path";
|
|
1691
1747
|
import { existsSync as existsSync5 } from "fs";
|
|
1692
1748
|
import { readdir as readdir2 } from "fs/promises";
|
|
1693
1749
|
import * as p from "@clack/prompts";
|
|
@@ -1717,24 +1773,24 @@ var selectConfig = async () => {
|
|
|
1717
1773
|
};
|
|
1718
1774
|
var findAllDeployables = async (srcPath) => {
|
|
1719
1775
|
const functions = [];
|
|
1720
|
-
const functionsPath =
|
|
1776
|
+
const functionsPath = join5(srcPath, "function");
|
|
1721
1777
|
for (const dir of FUNCTION_DIRS2) {
|
|
1722
|
-
const dirPath =
|
|
1778
|
+
const dirPath = join5(functionsPath, dir);
|
|
1723
1779
|
try {
|
|
1724
1780
|
const items = await readdir2(dirPath, { withFileTypes: true });
|
|
1725
1781
|
for (const item of items) {
|
|
1726
1782
|
if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
|
|
1727
|
-
functions.push(
|
|
1783
|
+
functions.push(join5(dirPath, item.name));
|
|
1728
1784
|
}
|
|
1729
1785
|
}
|
|
1730
1786
|
} catch {}
|
|
1731
1787
|
}
|
|
1732
|
-
const reactEntry =
|
|
1788
|
+
const reactEntry = join5(srcPath, "index.tsx");
|
|
1733
1789
|
const hasReact = existsSync5(reactEntry);
|
|
1734
1790
|
return { functions, reactEntry: hasReact ? reactEntry : null };
|
|
1735
1791
|
};
|
|
1736
1792
|
var deployAll = async (config, options) => {
|
|
1737
|
-
const srcPath =
|
|
1793
|
+
const srcPath = join5(process.cwd(), "src");
|
|
1738
1794
|
if (!existsSync5(srcPath)) {
|
|
1739
1795
|
console.error(chalk3.red("\u2717 No src directory found"));
|
|
1740
1796
|
process.exit(1);
|
|
@@ -1758,7 +1814,7 @@ var deployAll = async (config, options) => {
|
|
|
1758
1814
|
const fileName = basename4(filePath);
|
|
1759
1815
|
try {
|
|
1760
1816
|
console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
|
|
1761
|
-
const result = await deployFunction(filePath, privateKey, config.api);
|
|
1817
|
+
const result = await deployFunction(filePath, privateKey, config.api, options.curl);
|
|
1762
1818
|
if (result.skipped) {
|
|
1763
1819
|
console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
|
|
1764
1820
|
skipped++;
|
|
@@ -1775,7 +1831,7 @@ var deployAll = async (config, options) => {
|
|
|
1775
1831
|
const fileName = basename4(reactEntry);
|
|
1776
1832
|
try {
|
|
1777
1833
|
console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
|
|
1778
|
-
const result = await deployReact(reactEntry, privateKey, config.api);
|
|
1834
|
+
const result = await deployReact(reactEntry, privateKey, config.api, options.curl);
|
|
1779
1835
|
if (result.skipped) {
|
|
1780
1836
|
console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
|
|
1781
1837
|
skipped++;
|
|
@@ -1797,7 +1853,7 @@ var deployAll = async (config, options) => {
|
|
|
1797
1853
|
}
|
|
1798
1854
|
};
|
|
1799
1855
|
var registerDeployCommand = (program) => {
|
|
1800
|
-
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) => {
|
|
1856
|
+
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").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (file, options) => {
|
|
1801
1857
|
let config;
|
|
1802
1858
|
if (options.env) {
|
|
1803
1859
|
config = getConfig(options.env);
|
|
@@ -1831,7 +1887,7 @@ var registerDeployCommand = (program) => {
|
|
|
1831
1887
|
try {
|
|
1832
1888
|
if (isReact(filePath)) {
|
|
1833
1889
|
console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
|
|
1834
|
-
const result = await deployReact(filePath, privateKey, config.api);
|
|
1890
|
+
const result = await deployReact(filePath, privateKey, config.api, options.curl);
|
|
1835
1891
|
if (result.skipped) {
|
|
1836
1892
|
console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
|
|
1837
1893
|
} else {
|
|
@@ -1839,7 +1895,7 @@ var registerDeployCommand = (program) => {
|
|
|
1839
1895
|
}
|
|
1840
1896
|
} else {
|
|
1841
1897
|
console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
|
|
1842
|
-
const result = await deployFunction(filePath, privateKey, config.api);
|
|
1898
|
+
const result = await deployFunction(filePath, privateKey, config.api, options.curl);
|
|
1843
1899
|
if (result.skipped) {
|
|
1844
1900
|
console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
|
|
1845
1901
|
} else {
|
|
@@ -1986,14 +2042,72 @@ var printAvailableCommands = () => {
|
|
|
1986
2042
|
console.log(chalk5.cyan(" nulljs host") + chalk5.gray(" - Set up production hosting with systemd"));
|
|
1987
2043
|
};
|
|
1988
2044
|
// src/commands/secret.ts
|
|
1989
|
-
init_api();
|
|
1990
|
-
init_config();
|
|
1991
2045
|
import { Command as Command2 } from "commander";
|
|
1992
2046
|
import chalk6 from "chalk";
|
|
1993
2047
|
import { resolve as resolve3 } from "path";
|
|
1994
2048
|
import { readFile, readdir as readdir3 } from "fs/promises";
|
|
1995
2049
|
import { existsSync as existsSync6 } from "fs";
|
|
1996
2050
|
import * as p3 from "@clack/prompts";
|
|
2051
|
+
|
|
2052
|
+
// ../../packages/api/actions/secrets.ts
|
|
2053
|
+
init_http();
|
|
2054
|
+
var createSignatureHeader = async (privateKey, props) => {
|
|
2055
|
+
const timestamp = new Date().toISOString();
|
|
2056
|
+
const raw = `${props.method}-${props.path}-${timestamp}${props.body ? "-" + props.body : ""}`;
|
|
2057
|
+
const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(raw));
|
|
2058
|
+
const signature = btoa(String.fromCharCode(...new Uint8Array(sign)));
|
|
2059
|
+
const header = {
|
|
2060
|
+
authorization: signature,
|
|
2061
|
+
"x-time": timestamp
|
|
2062
|
+
};
|
|
2063
|
+
if (props.body) {
|
|
2064
|
+
header["x-body"] = btoa(props.body);
|
|
2065
|
+
}
|
|
2066
|
+
return header;
|
|
2067
|
+
};
|
|
2068
|
+
var listSecrets = async (options) => {
|
|
2069
|
+
const baseUrl = options.url || "";
|
|
2070
|
+
const path = "/api/secrets";
|
|
2071
|
+
const fullPath = `${baseUrl}${path}`;
|
|
2072
|
+
const headers = await createSignatureHeader(options.privateKey, {
|
|
2073
|
+
method: "GET",
|
|
2074
|
+
path: fullPath
|
|
2075
|
+
});
|
|
2076
|
+
const result = await httpGet(path, {
|
|
2077
|
+
baseUrl,
|
|
2078
|
+
useCurl: options.useCurl,
|
|
2079
|
+
headers
|
|
2080
|
+
});
|
|
2081
|
+
if (!result.ok) {
|
|
2082
|
+
throw new Error(`Failed to list secrets (${result.status}): ${result.body}`);
|
|
2083
|
+
}
|
|
2084
|
+
return JSON.parse(result.body);
|
|
2085
|
+
};
|
|
2086
|
+
var createSecret = async (secret, options) => {
|
|
2087
|
+
const baseUrl = options.url || "";
|
|
2088
|
+
const path = "/api/secrets";
|
|
2089
|
+
const fullPath = `${baseUrl}${path}`;
|
|
2090
|
+
const body = JSON.stringify(secret);
|
|
2091
|
+
const signatureHeaders = await createSignatureHeader(options.privateKey, {
|
|
2092
|
+
method: "POST",
|
|
2093
|
+
path: fullPath,
|
|
2094
|
+
body
|
|
2095
|
+
});
|
|
2096
|
+
const result = await httpPost(path, body, {
|
|
2097
|
+
baseUrl,
|
|
2098
|
+
useCurl: options.useCurl,
|
|
2099
|
+
headers: {
|
|
2100
|
+
"Content-Type": "application/json",
|
|
2101
|
+
...signatureHeaders
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
if (!result.ok) {
|
|
2105
|
+
throw new Error(`Failed to create secret (${result.status}): ${result.body}`);
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
// src/commands/secret.ts
|
|
2110
|
+
init_config();
|
|
1997
2111
|
var selectConfig2 = async () => {
|
|
1998
2112
|
const configList = listConfigs();
|
|
1999
2113
|
if (!configList || configList.configs.length === 0) {
|
|
@@ -2048,7 +2162,7 @@ var parseEnvFile = async (filePath) => {
|
|
|
2048
2162
|
const content = await readFile(filePath, "utf-8");
|
|
2049
2163
|
const lines = content.split(`
|
|
2050
2164
|
`);
|
|
2051
|
-
const
|
|
2165
|
+
const secrets = [];
|
|
2052
2166
|
for (const line of lines) {
|
|
2053
2167
|
const trimmed = line.trim();
|
|
2054
2168
|
if (!trimmed || trimmed.startsWith("#")) {
|
|
@@ -2064,13 +2178,13 @@ var parseEnvFile = async (filePath) => {
|
|
|
2064
2178
|
value = value.slice(1, -1);
|
|
2065
2179
|
}
|
|
2066
2180
|
if (key) {
|
|
2067
|
-
|
|
2181
|
+
secrets.push({ key, value });
|
|
2068
2182
|
}
|
|
2069
2183
|
}
|
|
2070
|
-
return
|
|
2184
|
+
return secrets;
|
|
2071
2185
|
};
|
|
2072
2186
|
var registerSecretCommand = (program) => {
|
|
2073
|
-
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) => {
|
|
2187
|
+
program.command("secret").description("Secret management").addCommand(new Command2("list").description("List all secret keys").option("-e, --env <name>", "Use a specific config environment").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (options) => {
|
|
2074
2188
|
let config;
|
|
2075
2189
|
try {
|
|
2076
2190
|
config = options.env ? getConfig(options.env) : await selectConfig2();
|
|
@@ -2087,7 +2201,7 @@ var registerSecretCommand = (program) => {
|
|
|
2087
2201
|
try {
|
|
2088
2202
|
console.log(chalk6.gray(`Fetching secrets from ${config.api}...`));
|
|
2089
2203
|
const privateKey = await loadPrivateKey(config);
|
|
2090
|
-
const keys = await listSecrets({ privateKey, url: config.api });
|
|
2204
|
+
const keys = await listSecrets({ privateKey, url: config.api, useCurl: options.curl });
|
|
2091
2205
|
if (keys.length === 0) {
|
|
2092
2206
|
console.log(chalk6.yellow("No secrets found"));
|
|
2093
2207
|
return;
|
|
@@ -2101,7 +2215,7 @@ Secret keys:`));
|
|
|
2101
2215
|
console.error(chalk6.red("\u2717 Failed to list secrets:"), error instanceof Error ? error.message : String(error));
|
|
2102
2216
|
process.exit(1);
|
|
2103
2217
|
}
|
|
2104
|
-
})).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) => {
|
|
2218
|
+
})).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").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (key, value, options) => {
|
|
2105
2219
|
const config = options?.env ? getConfig(options.env) : await selectConfig2();
|
|
2106
2220
|
if (!config) {
|
|
2107
2221
|
console.error(chalk6.red("\u2717 No configuration found."));
|
|
@@ -2147,13 +2261,13 @@ Secret keys:`));
|
|
|
2147
2261
|
await new Promise((resolve4) => setImmediate(resolve4));
|
|
2148
2262
|
try {
|
|
2149
2263
|
const privateKey = await loadPrivateKey(config);
|
|
2150
|
-
await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api });
|
|
2264
|
+
await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api, useCurl: options?.curl });
|
|
2151
2265
|
console.log(chalk6.green("\u2713 Secret created:") + ` ${chalk6.cyan(secretKey)}`);
|
|
2152
2266
|
} catch (error) {
|
|
2153
2267
|
console.error(chalk6.red("\u2717 Failed to create secret:"), error instanceof Error ? error.message : error);
|
|
2154
2268
|
process.exit(1);
|
|
2155
2269
|
}
|
|
2156
|
-
})).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) => {
|
|
2270
|
+
})).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").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (file, options) => {
|
|
2157
2271
|
const config = options?.env ? getConfig(options.env) : await selectConfig2();
|
|
2158
2272
|
if (!config) {
|
|
2159
2273
|
console.error(chalk6.red("\u2717 No configuration found."));
|
|
@@ -2173,19 +2287,19 @@ Secret keys:`));
|
|
|
2173
2287
|
}
|
|
2174
2288
|
await new Promise((resolve4) => setImmediate(resolve4));
|
|
2175
2289
|
try {
|
|
2176
|
-
const
|
|
2177
|
-
if (
|
|
2290
|
+
const secrets = await parseEnvFile(filePath);
|
|
2291
|
+
if (secrets.length === 0) {
|
|
2178
2292
|
console.log(chalk6.yellow("No secrets found in file"));
|
|
2179
2293
|
return;
|
|
2180
2294
|
}
|
|
2181
2295
|
const privateKey = await loadPrivateKey(config);
|
|
2182
2296
|
let created = 0;
|
|
2183
2297
|
let failed = 0;
|
|
2184
|
-
console.log(chalk6.cyan(`Deploying ${
|
|
2298
|
+
console.log(chalk6.cyan(`Deploying ${secrets.length} secret(s) to ${config.name}...
|
|
2185
2299
|
`));
|
|
2186
|
-
for (const secret of
|
|
2300
|
+
for (const secret of secrets) {
|
|
2187
2301
|
try {
|
|
2188
|
-
await createSecret(secret, { privateKey, url: config.api });
|
|
2302
|
+
await createSecret(secret, { privateKey, url: config.api, useCurl: options?.curl });
|
|
2189
2303
|
console.log(chalk6.green("\u2713") + ` ${secret.key}`);
|
|
2190
2304
|
created++;
|
|
2191
2305
|
} catch (error) {
|
|
@@ -2203,7 +2317,7 @@ Secret keys:`));
|
|
|
2203
2317
|
console.error(chalk6.red("\u2717 Failed to deploy secrets:"), error instanceof Error ? error.message : error);
|
|
2204
2318
|
process.exit(1);
|
|
2205
2319
|
}
|
|
2206
|
-
})).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) => {
|
|
2320
|
+
})).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").option("--curl", "Use curl instead of fetch (workaround for macOS local network issues)").action(async (file, options) => {
|
|
2207
2321
|
const config = options?.env ? getConfig(options.env) : await selectConfig2();
|
|
2208
2322
|
if (!config) {
|
|
2209
2323
|
console.error(chalk6.red("\u2717 No configuration found."));
|
|
@@ -2223,19 +2337,19 @@ Secret keys:`));
|
|
|
2223
2337
|
}
|
|
2224
2338
|
await new Promise((resolve4) => setImmediate(resolve4));
|
|
2225
2339
|
try {
|
|
2226
|
-
const
|
|
2227
|
-
if (
|
|
2340
|
+
const secrets = await parseEnvFile(filePath);
|
|
2341
|
+
if (secrets.length === 0) {
|
|
2228
2342
|
console.log(chalk6.yellow("No secrets found in file"));
|
|
2229
2343
|
return;
|
|
2230
2344
|
}
|
|
2231
2345
|
const privateKey = await loadPrivateKey(config);
|
|
2232
2346
|
let created = 0;
|
|
2233
2347
|
let failed = 0;
|
|
2234
|
-
console.log(chalk6.cyan(`Importing ${
|
|
2348
|
+
console.log(chalk6.cyan(`Importing ${secrets.length} secret(s) to ${config.name}...
|
|
2235
2349
|
`));
|
|
2236
|
-
for (const secret of
|
|
2350
|
+
for (const secret of secrets) {
|
|
2237
2351
|
try {
|
|
2238
|
-
await createSecret(secret, { privateKey, url: config.api });
|
|
2352
|
+
await createSecret(secret, { privateKey, url: config.api, useCurl: options?.curl });
|
|
2239
2353
|
console.log(chalk6.green("\u2713") + ` ${secret.key}`);
|
|
2240
2354
|
created++;
|
|
2241
2355
|
} catch (error) {
|
|
@@ -2256,10 +2370,10 @@ Secret keys:`));
|
|
|
2256
2370
|
}));
|
|
2257
2371
|
};
|
|
2258
2372
|
// src/commands/host.ts
|
|
2259
|
-
import { existsSync as existsSync7, writeFileSync as
|
|
2260
|
-
import { join as
|
|
2261
|
-
import { execSync } from "child_process";
|
|
2373
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
|
|
2374
|
+
import { join as join6 } from "path";
|
|
2262
2375
|
import { homedir } from "os";
|
|
2376
|
+
var {$: $2 } = globalThis.Bun;
|
|
2263
2377
|
import chalk7 from "chalk";
|
|
2264
2378
|
var PLATFORMS2 = {
|
|
2265
2379
|
"linux-x64": "@tothalex/nulljs-linux-x64",
|
|
@@ -2289,8 +2403,8 @@ var generateProductionKeys = async () => {
|
|
|
2289
2403
|
const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)));
|
|
2290
2404
|
return { privateKey, publicKey };
|
|
2291
2405
|
};
|
|
2292
|
-
var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
|
|
2293
|
-
const configPath =
|
|
2406
|
+
var saveProductionConfig = async (cloudPath, privateKey, publicKey) => {
|
|
2407
|
+
const configPath = join6(cloudPath, "config.json");
|
|
2294
2408
|
const config = {
|
|
2295
2409
|
key: {
|
|
2296
2410
|
private: privateKey,
|
|
@@ -2298,8 +2412,8 @@ var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
|
|
|
2298
2412
|
}
|
|
2299
2413
|
};
|
|
2300
2414
|
console.log(chalk7.blue("Saving production configuration..."));
|
|
2301
|
-
|
|
2302
|
-
|
|
2415
|
+
writeFileSync3(configPath, JSON.stringify(config, null, 2));
|
|
2416
|
+
await $2`chmod 600 ${configPath}`;
|
|
2303
2417
|
console.log(chalk7.green("Production configuration saved"));
|
|
2304
2418
|
};
|
|
2305
2419
|
var createSystemdService = (cloudPath, publicKey, serverBinPath, userName) => {
|
|
@@ -2337,14 +2451,15 @@ var ensureCloudDirectory = (cloudPath) => {
|
|
|
2337
2451
|
console.log(chalk7.gray(`Cloud directory already exists: ${cloudPath}`));
|
|
2338
2452
|
}
|
|
2339
2453
|
};
|
|
2340
|
-
var installSystemdService = (serviceContent) => {
|
|
2454
|
+
var installSystemdService = async (serviceContent) => {
|
|
2341
2455
|
const servicePath = "/etc/systemd/system/nulljs.service";
|
|
2342
2456
|
console.log(chalk7.blue("Installing systemd service..."));
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2457
|
+
writeFileSync3("/tmp/nulljs.service", serviceContent);
|
|
2458
|
+
await $2`sudo mv /tmp/nulljs.service ${servicePath}`;
|
|
2459
|
+
await $2`sudo systemctl daemon-reload`;
|
|
2460
|
+
await $2`sudo systemctl enable nulljs.service`;
|
|
2461
|
+
await $2`sudo systemctl start nulljs.service`;
|
|
2462
|
+
console.log(chalk7.green("Systemd service installed, enabled and started"));
|
|
2348
2463
|
};
|
|
2349
2464
|
var validateLinux = () => {
|
|
2350
2465
|
if (process.platform !== "linux") {
|
|
@@ -2360,7 +2475,7 @@ var validateServerBinary = (serverBinPath) => {
|
|
|
2360
2475
|
}
|
|
2361
2476
|
};
|
|
2362
2477
|
var checkExistingProductionConfig = (cloudPath) => {
|
|
2363
|
-
const configPath =
|
|
2478
|
+
const configPath = join6(cloudPath, "config.json");
|
|
2364
2479
|
if (!existsSync7(configPath)) {
|
|
2365
2480
|
return null;
|
|
2366
2481
|
}
|
|
@@ -2380,13 +2495,13 @@ var unhost = async (options = {}) => {
|
|
|
2380
2495
|
try {
|
|
2381
2496
|
console.log(chalk7.blue("Stopping NullJS service..."));
|
|
2382
2497
|
try {
|
|
2383
|
-
|
|
2498
|
+
await $2`sudo systemctl stop nulljs`;
|
|
2384
2499
|
console.log(chalk7.green("Service stopped"));
|
|
2385
2500
|
} catch {
|
|
2386
2501
|
console.log(chalk7.yellow("Service was not running"));
|
|
2387
2502
|
}
|
|
2388
2503
|
try {
|
|
2389
|
-
|
|
2504
|
+
await $2`sudo systemctl disable nulljs`;
|
|
2390
2505
|
console.log(chalk7.green("Service disabled"));
|
|
2391
2506
|
} catch {
|
|
2392
2507
|
console.log(chalk7.yellow("Service was not enabled"));
|
|
@@ -2394,15 +2509,15 @@ var unhost = async (options = {}) => {
|
|
|
2394
2509
|
const servicePath = "/etc/systemd/system/nulljs.service";
|
|
2395
2510
|
if (existsSync7(servicePath)) {
|
|
2396
2511
|
console.log(chalk7.blue("Removing systemd service file..."));
|
|
2397
|
-
|
|
2398
|
-
|
|
2512
|
+
await $2`sudo rm ${servicePath}`;
|
|
2513
|
+
await $2`sudo systemctl daemon-reload`;
|
|
2399
2514
|
console.log(chalk7.green("Systemd service file removed"));
|
|
2400
2515
|
}
|
|
2401
2516
|
if (!options.keepData) {
|
|
2402
|
-
const defaultCloudPath =
|
|
2517
|
+
const defaultCloudPath = join6(homedir(), ".nulljs");
|
|
2403
2518
|
if (existsSync7(defaultCloudPath)) {
|
|
2404
2519
|
console.log(chalk7.blue("Removing cloud directory..."));
|
|
2405
|
-
|
|
2520
|
+
await $2`rm -rf ${defaultCloudPath}`;
|
|
2406
2521
|
console.log(chalk7.green("Cloud directory removed"));
|
|
2407
2522
|
} else {
|
|
2408
2523
|
console.log(chalk7.gray("Cloud directory does not exist"));
|
|
@@ -2422,9 +2537,42 @@ var unhost = async (options = {}) => {
|
|
|
2422
2537
|
process.exit(1);
|
|
2423
2538
|
}
|
|
2424
2539
|
};
|
|
2540
|
+
var update = async () => {
|
|
2541
|
+
validateLinux();
|
|
2542
|
+
const cloudPath = join6(homedir(), ".nulljs");
|
|
2543
|
+
const servicePath = "/etc/systemd/system/nulljs.service";
|
|
2544
|
+
if (!existsSync7(servicePath)) {
|
|
2545
|
+
console.log(chalk7.red("NullJS service is not installed."));
|
|
2546
|
+
console.log(chalk7.gray('Run "nulljs host" to set up hosting first.'));
|
|
2547
|
+
process.exit(1);
|
|
2548
|
+
}
|
|
2549
|
+
const productionConfig = checkExistingProductionConfig(cloudPath);
|
|
2550
|
+
if (!productionConfig) {
|
|
2551
|
+
console.log(chalk7.red("Production config not found."));
|
|
2552
|
+
console.log(chalk7.gray('Run "nulljs host" to set up hosting first.'));
|
|
2553
|
+
process.exit(1);
|
|
2554
|
+
}
|
|
2555
|
+
console.log(chalk7.blue("Updating NullJS service..."));
|
|
2556
|
+
console.log(chalk7.blue("Stopping service..."));
|
|
2557
|
+
try {
|
|
2558
|
+
await $2`sudo systemctl stop nulljs`;
|
|
2559
|
+
} catch {}
|
|
2560
|
+
const serverBinPath = getServerBinPath();
|
|
2561
|
+
validateServerBinary(serverBinPath);
|
|
2562
|
+
console.log(chalk7.gray(` Binary path: ${serverBinPath}`));
|
|
2563
|
+
const currentUser = process.env.USER || process.env.USERNAME || "root";
|
|
2564
|
+
const serviceContent = createSystemdService(cloudPath, productionConfig.key.public, serverBinPath, currentUser);
|
|
2565
|
+
console.log(chalk7.blue("Updating systemd service..."));
|
|
2566
|
+
writeFileSync3("/tmp/nulljs.service", serviceContent);
|
|
2567
|
+
await $2`sudo mv /tmp/nulljs.service ${servicePath}`;
|
|
2568
|
+
await $2`sudo systemctl daemon-reload`;
|
|
2569
|
+
console.log(chalk7.blue("Starting service..."));
|
|
2570
|
+
await $2`sudo systemctl start nulljs.service`;
|
|
2571
|
+
console.log(chalk7.green("NullJS service updated and restarted!"));
|
|
2572
|
+
};
|
|
2425
2573
|
var host = async (cloudPath) => {
|
|
2426
2574
|
validateLinux();
|
|
2427
|
-
const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath :
|
|
2575
|
+
const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath : join6(process.cwd(), cloudPath) : join6(homedir(), ".nulljs");
|
|
2428
2576
|
console.log(chalk7.blue("Setting up NullJS production hosting..."));
|
|
2429
2577
|
console.log(chalk7.gray(` Cloud path: ${resolvedCloudPath}`));
|
|
2430
2578
|
const serverBinPath = getServerBinPath();
|
|
@@ -2435,7 +2583,7 @@ var host = async (cloudPath) => {
|
|
|
2435
2583
|
let productionConfig = checkExistingProductionConfig(resolvedCloudPath);
|
|
2436
2584
|
if (!productionConfig) {
|
|
2437
2585
|
const { privateKey, publicKey } = await generateProductionKeys();
|
|
2438
|
-
saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
|
|
2586
|
+
await saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
|
|
2439
2587
|
productionConfig = { key: { private: privateKey, public: publicKey } };
|
|
2440
2588
|
console.log(chalk7.green("Production keys generated"));
|
|
2441
2589
|
console.log(chalk7.blue("Production Public Key:"));
|
|
@@ -2446,32 +2594,33 @@ var host = async (cloudPath) => {
|
|
|
2446
2594
|
console.log(chalk7.gray(productionConfig.key.public));
|
|
2447
2595
|
}
|
|
2448
2596
|
const serviceContent = createSystemdService(resolvedCloudPath, productionConfig.key.public, serverBinPath, currentUser);
|
|
2449
|
-
installSystemdService(serviceContent);
|
|
2597
|
+
await installSystemdService(serviceContent);
|
|
2450
2598
|
console.log(chalk7.green("NullJS hosting setup complete!"));
|
|
2451
2599
|
console.log("");
|
|
2452
2600
|
console.log(chalk7.blue("Service management commands:"));
|
|
2453
|
-
console.log(chalk7.gray(" Start: "), chalk7.white("sudo systemctl start nulljs"));
|
|
2454
2601
|
console.log(chalk7.gray(" Stop: "), chalk7.white("sudo systemctl stop nulljs"));
|
|
2602
|
+
console.log(chalk7.gray(" Restart: "), chalk7.white("sudo systemctl restart nulljs"));
|
|
2455
2603
|
console.log(chalk7.gray(" Status: "), chalk7.white("sudo systemctl status nulljs"));
|
|
2456
2604
|
console.log(chalk7.gray(" Logs: "), chalk7.white("sudo journalctl -u nulljs -f"));
|
|
2457
|
-
console.log("");
|
|
2458
|
-
console.log(chalk7.yellow("Remember to start the service: sudo systemctl start nulljs"));
|
|
2459
2605
|
};
|
|
2460
2606
|
var registerHostCommand = (program) => {
|
|
2461
2607
|
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) => {
|
|
2462
2608
|
await host(cloudPath);
|
|
2463
2609
|
});
|
|
2610
|
+
hostCmd.command("update").description("Update NullJS service after package upgrade").action(async () => {
|
|
2611
|
+
await update();
|
|
2612
|
+
});
|
|
2464
2613
|
hostCmd.command("remove").description("Remove NullJS production hosting").option("--keep-data", "Keep cloud data directory").action(async (options) => {
|
|
2465
2614
|
await unhost(options);
|
|
2466
2615
|
});
|
|
2467
|
-
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) => {
|
|
2616
|
+
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(async (options) => {
|
|
2468
2617
|
validateLinux();
|
|
2469
|
-
const args = ["-u", "nulljs", "-n", options.lines];
|
|
2470
|
-
if (options.follow) {
|
|
2471
|
-
args.push("-f");
|
|
2472
|
-
}
|
|
2473
2618
|
try {
|
|
2474
|
-
|
|
2619
|
+
if (options.follow) {
|
|
2620
|
+
await $2`journalctl -u nulljs -n ${options.lines} -f`;
|
|
2621
|
+
} else {
|
|
2622
|
+
await $2`journalctl -u nulljs -n ${options.lines}`;
|
|
2623
|
+
}
|
|
2475
2624
|
} catch {}
|
|
2476
2625
|
});
|
|
2477
2626
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tothalex/nulljs",
|
|
3
3
|
"module": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.60",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nulljs": "./dist/cli.js"
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"typescript": "^5"
|
|
19
19
|
},
|
|
20
20
|
"optionalDependencies": {
|
|
21
|
-
"@tothalex/nulljs-darwin-arm64": "^0.0.
|
|
22
|
-
"@tothalex/nulljs-linux-arm64": "^0.0.
|
|
23
|
-
"@tothalex/nulljs-linux-x64": "^0.0.
|
|
21
|
+
"@tothalex/nulljs-darwin-arm64": "^0.0.86",
|
|
22
|
+
"@tothalex/nulljs-linux-arm64": "^0.0.86",
|
|
23
|
+
"@tothalex/nulljs-linux-x64": "^0.0.86"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist"
|