@tothalex/nulljs 0.0.59 → 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 +268 -199
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -52,113 +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
|
-
import { mkdtempSync, writeFileSync, rmSync } from "fs";
|
|
57
|
-
import { tmpdir } from "os";
|
|
58
|
-
import { join } from "path";
|
|
59
|
-
var {$ } = globalThis.Bun;
|
|
60
|
-
var getMimeType = (fileName) => {
|
|
61
|
-
if (fileName.endsWith(".js"))
|
|
62
|
-
return "application/javascript";
|
|
63
|
-
if (fileName.endsWith(".css"))
|
|
64
|
-
return "text/css";
|
|
65
|
-
if (fileName.endsWith(".html"))
|
|
66
|
-
return "text/html";
|
|
67
|
-
if (fileName.endsWith(".json"))
|
|
68
|
-
return "application/json";
|
|
69
|
-
return "application/octet-stream";
|
|
70
|
-
}, createFormDataWithInfo = (assets2) => {
|
|
71
|
-
const form = new FormData;
|
|
72
|
-
const blobs = [];
|
|
73
|
-
for (const asset of assets2) {
|
|
74
|
-
const mime = getMimeType(asset.fileName);
|
|
75
|
-
const buffer = Buffer.from(asset.code);
|
|
76
|
-
const blob = new Blob([buffer], { type: mime });
|
|
77
|
-
form.append(asset.fileName, blob);
|
|
78
|
-
blobs.push({ fileName: asset.fileName, size: blob.size, type: mime });
|
|
79
|
-
}
|
|
80
|
-
return { form, blobs };
|
|
81
|
-
}, createSignature = async (blobs, privateKey) => {
|
|
82
|
-
const sortedBlobs = [...blobs].sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
83
|
-
const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
|
|
84
|
-
const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
|
|
85
|
-
return btoa(String.fromCharCode(...new Uint8Array(sign)));
|
|
86
|
-
}, postWithCurl = async (url, signature, assets2) => {
|
|
87
|
-
const tempDir = mkdtempSync(join(tmpdir(), "nulljs-deploy-"));
|
|
88
|
-
try {
|
|
89
|
-
const formParts = [];
|
|
90
|
-
for (const asset of assets2) {
|
|
91
|
-
const filePath = join(tempDir, asset.fileName);
|
|
92
|
-
writeFileSync(filePath, asset.code);
|
|
93
|
-
const mime = getMimeType(asset.fileName);
|
|
94
|
-
formParts.push(`-F`);
|
|
95
|
-
formParts.push(`${asset.fileName}=@${filePath};type=${mime}`);
|
|
96
|
-
}
|
|
97
|
-
const result = await $`curl -s -w '\n%{http_code}' -X POST -H "authorization: ${signature}" ${formParts} ${url}`.text();
|
|
98
|
-
const lines = result.trim().split(`
|
|
99
|
-
`);
|
|
100
|
-
const statusCode = parseInt(lines.pop() || "0", 10);
|
|
101
|
-
const body = lines.join(`
|
|
102
|
-
`);
|
|
103
|
-
return { ok: statusCode >= 200 && statusCode < 300, status: statusCode, text: body };
|
|
104
|
-
} finally {
|
|
105
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
106
|
-
}
|
|
107
|
-
}, createFunctionDeployment = async (deployment2, options) => {
|
|
108
|
-
const handler = deployment2.assets.find((asset) => asset.fileName === "handler.js");
|
|
109
|
-
if (!handler) {
|
|
110
|
-
throw new Error(`Handler not found for ${deployment2.name}`);
|
|
111
|
-
}
|
|
112
|
-
const { blobs } = createFormDataWithInfo([handler]);
|
|
113
|
-
const signature = await createSignature(blobs, options.privateKey);
|
|
114
|
-
const baseUrl = options.url || "";
|
|
115
|
-
const url = `${baseUrl}/api/deployment`;
|
|
116
|
-
if (options.useCurl) {
|
|
117
|
-
const result = await postWithCurl(url, signature, [handler]);
|
|
118
|
-
if (!result.ok) {
|
|
119
|
-
throw new Error(`Deployment failed (${result.status}): ${result.text}`);
|
|
120
|
-
}
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const { form } = createFormDataWithInfo([handler]);
|
|
124
|
-
const response = await fetch(url, {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers: {
|
|
127
|
-
authorization: signature
|
|
128
|
-
},
|
|
129
|
-
body: form
|
|
130
|
-
});
|
|
131
|
-
if (!response.ok) {
|
|
132
|
-
const errorText = await response.text();
|
|
133
|
-
throw new Error(`Deployment failed (${response.status}): ${errorText}`);
|
|
134
|
-
}
|
|
135
|
-
}, createReactDeployment = async (deployment2, options) => {
|
|
136
|
-
const { blobs } = createFormDataWithInfo(deployment2.assets);
|
|
137
|
-
const signature = await createSignature(blobs, options.privateKey);
|
|
138
|
-
const baseUrl = options.url || "";
|
|
139
|
-
const url = `${baseUrl}/api/deployment/react`;
|
|
140
|
-
if (options.useCurl) {
|
|
141
|
-
const result = await postWithCurl(url, signature, deployment2.assets);
|
|
142
|
-
if (!result.ok) {
|
|
143
|
-
throw new Error(`Deployment failed (${result.status}): ${result.text}`);
|
|
144
|
-
}
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const { form } = createFormDataWithInfo(deployment2.assets);
|
|
148
|
-
const response = await fetch(url, {
|
|
149
|
-
method: "POST",
|
|
150
|
-
headers: {
|
|
151
|
-
authorization: signature
|
|
152
|
-
},
|
|
153
|
-
body: form
|
|
154
|
-
});
|
|
155
|
-
if (!response.ok) {
|
|
156
|
-
const errorText = await response.text();
|
|
157
|
-
throw new Error(`Deployment failed (${response.status}): ${errorText}`);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
var init_deploy = () => {};
|
|
161
|
-
|
|
162
55
|
// ../../packages/api/actions/invocations.ts
|
|
163
56
|
var init_invocations = () => {};
|
|
164
57
|
|
|
@@ -178,68 +71,9 @@ var init_assets = () => {};
|
|
|
178
71
|
// ../../packages/api/actions/activity.ts
|
|
179
72
|
var init_activity = () => {};
|
|
180
73
|
|
|
181
|
-
// ../../packages/api/actions/secrets.ts
|
|
182
|
-
var createSignatureHeader = async (privateKey, props) => {
|
|
183
|
-
const timestamp = new Date().toISOString();
|
|
184
|
-
const raw = `${props.method}-${props.path}-${timestamp}${props.body ? "-" + props.body : ""}`;
|
|
185
|
-
const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(raw));
|
|
186
|
-
const signature = btoa(String.fromCharCode(...new Uint8Array(sign)));
|
|
187
|
-
const header = {
|
|
188
|
-
authorization: signature,
|
|
189
|
-
"x-time": timestamp
|
|
190
|
-
};
|
|
191
|
-
if (props.body) {
|
|
192
|
-
header["x-body"] = btoa(props.body);
|
|
193
|
-
}
|
|
194
|
-
return header;
|
|
195
|
-
}, listSecrets = async (options) => {
|
|
196
|
-
const baseUrl = options.url || "";
|
|
197
|
-
const path = `${baseUrl}/api/secrets`;
|
|
198
|
-
const headers = await createSignatureHeader(options.privateKey, {
|
|
199
|
-
method: "GET",
|
|
200
|
-
path
|
|
201
|
-
});
|
|
202
|
-
let response;
|
|
203
|
-
try {
|
|
204
|
-
response = await fetch(path, {
|
|
205
|
-
method: "GET",
|
|
206
|
-
headers
|
|
207
|
-
});
|
|
208
|
-
} catch (err) {
|
|
209
|
-
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
210
|
-
}
|
|
211
|
-
if (!response.ok) {
|
|
212
|
-
const errorText = await response.text();
|
|
213
|
-
throw new Error(`Failed to list secrets (${response.status}): ${errorText}`);
|
|
214
|
-
}
|
|
215
|
-
return response.json();
|
|
216
|
-
}, createSecret = async (secret, options) => {
|
|
217
|
-
const baseUrl = options.url || "";
|
|
218
|
-
const path = `${baseUrl}/api/secrets`;
|
|
219
|
-
const body = JSON.stringify(secret);
|
|
220
|
-
const headers = await createSignatureHeader(options.privateKey, {
|
|
221
|
-
method: "POST",
|
|
222
|
-
path,
|
|
223
|
-
body
|
|
224
|
-
});
|
|
225
|
-
const response = await fetch(path, {
|
|
226
|
-
method: "POST",
|
|
227
|
-
headers: {
|
|
228
|
-
"Content-Type": "application/json",
|
|
229
|
-
...headers
|
|
230
|
-
},
|
|
231
|
-
body
|
|
232
|
-
});
|
|
233
|
-
if (!response.ok) {
|
|
234
|
-
const errorText = await response.text();
|
|
235
|
-
throw new Error(`Failed to create secret (${response.status}): ${errorText}`);
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
74
|
// ../../packages/api/actions/index.ts
|
|
240
75
|
var init_actions = __esm(() => {
|
|
241
76
|
init_deployments();
|
|
242
|
-
init_deploy();
|
|
243
77
|
init_invocations();
|
|
244
78
|
init_logs();
|
|
245
79
|
init_assets();
|
|
@@ -253,10 +87,10 @@ var init_api = __esm(() => {
|
|
|
253
87
|
});
|
|
254
88
|
|
|
255
89
|
// src/config/index.ts
|
|
256
|
-
import { readFileSync, writeFileSync
|
|
257
|
-
import { join
|
|
90
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
91
|
+
import { join } from "path";
|
|
258
92
|
import chalk from "chalk";
|
|
259
|
-
var getLocalConfigDir = () =>
|
|
93
|
+
var getLocalConfigDir = () => join(process.cwd(), ".nulljs"), getLocalConfigFile = () => join(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
|
|
260
94
|
const localDir = getLocalConfigDir();
|
|
261
95
|
if (!existsSync(localDir)) {
|
|
262
96
|
mkdirSync(localDir, { recursive: true });
|
|
@@ -274,7 +108,7 @@ var getLocalConfigDir = () => join2(process.cwd(), ".nulljs"), getLocalConfigFil
|
|
|
274
108
|
}
|
|
275
109
|
}, writeConfigStore = (store) => {
|
|
276
110
|
ensureLocalConfigDir();
|
|
277
|
-
|
|
111
|
+
writeFileSync(getLocalConfigFile(), JSON.stringify(store, null, 2));
|
|
278
112
|
}, readLocalConfig = () => {
|
|
279
113
|
const store = readConfigStore();
|
|
280
114
|
if (!store)
|
|
@@ -373,14 +207,14 @@ var init_config = () => {};
|
|
|
373
207
|
|
|
374
208
|
// src/lib/server.ts
|
|
375
209
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "fs";
|
|
376
|
-
import { join as
|
|
210
|
+
import { join as join2, dirname } from "path";
|
|
377
211
|
import chalk2 from "chalk";
|
|
378
212
|
var CLI_DIR, PLATFORMS, getPlatformKey = () => {
|
|
379
213
|
return `${process.platform}-${process.arch}`;
|
|
380
214
|
}, findProjectRoot = (startPath = process.cwd()) => {
|
|
381
215
|
let currentPath = startPath;
|
|
382
216
|
while (currentPath !== dirname(currentPath)) {
|
|
383
|
-
if (existsSync2(
|
|
217
|
+
if (existsSync2(join2(currentPath, "package.json"))) {
|
|
384
218
|
return currentPath;
|
|
385
219
|
}
|
|
386
220
|
currentPath = dirname(currentPath);
|
|
@@ -389,7 +223,7 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
|
|
|
389
223
|
}, findMonorepoRoot = (startPath = process.cwd()) => {
|
|
390
224
|
let currentPath = startPath;
|
|
391
225
|
while (currentPath !== dirname(currentPath)) {
|
|
392
|
-
if (existsSync2(
|
|
226
|
+
if (existsSync2(join2(currentPath, "Cargo.toml")) && existsSync2(join2(currentPath, "package.json"))) {
|
|
393
227
|
return currentPath;
|
|
394
228
|
}
|
|
395
229
|
currentPath = dirname(currentPath);
|
|
@@ -402,11 +236,11 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
|
|
|
402
236
|
}
|
|
403
237
|
const monorepoRoot = findMonorepoRoot() || findMonorepoRoot(CLI_DIR);
|
|
404
238
|
if (monorepoRoot) {
|
|
405
|
-
const debugBinary =
|
|
239
|
+
const debugBinary = join2(monorepoRoot, "target", "debug", "server");
|
|
406
240
|
if (existsSync2(debugBinary)) {
|
|
407
241
|
return { path: debugBinary, source: "local-debug" };
|
|
408
242
|
}
|
|
409
|
-
const releaseBinary =
|
|
243
|
+
const releaseBinary = join2(monorepoRoot, "target", "release", "server");
|
|
410
244
|
if (existsSync2(releaseBinary)) {
|
|
411
245
|
return { path: releaseBinary, source: "local-release" };
|
|
412
246
|
}
|
|
@@ -429,7 +263,7 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
|
|
|
429
263
|
args.push("--api-port", "3000");
|
|
430
264
|
args.push("--gateway-port", "3001");
|
|
431
265
|
const projectRoot = findProjectRoot();
|
|
432
|
-
const cloudPath =
|
|
266
|
+
const cloudPath = join2(projectRoot, ".nulljs");
|
|
433
267
|
args.push("--cloud-path", cloudPath);
|
|
434
268
|
if (!existsSync2(cloudPath)) {
|
|
435
269
|
mkdirSync2(cloudPath, { recursive: true });
|
|
@@ -680,6 +514,183 @@ var init_bundle = __esm(() => {
|
|
|
680
514
|
init_function();
|
|
681
515
|
});
|
|
682
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
|
+
|
|
683
694
|
// src/lib/deploy.ts
|
|
684
695
|
var exports_deploy = {};
|
|
685
696
|
__export(exports_deploy, {
|
|
@@ -730,7 +741,7 @@ var deployedHashes, clearDeployCache = () => {
|
|
|
730
741
|
return { deployed: true, skipped: false };
|
|
731
742
|
};
|
|
732
743
|
var init_deploy2 = __esm(() => {
|
|
733
|
-
|
|
744
|
+
init_deploy();
|
|
734
745
|
init_bundle();
|
|
735
746
|
deployedHashes = new Map;
|
|
736
747
|
});
|
|
@@ -912,7 +923,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
|
|
|
912
923
|
var init_watcher = __esm(() => {
|
|
913
924
|
init_config();
|
|
914
925
|
init_bundle();
|
|
915
|
-
|
|
926
|
+
init_deploy();
|
|
916
927
|
FUNCTION_DIRS = ["api", "cron", "event"];
|
|
917
928
|
deployedHashes2 = new Map;
|
|
918
929
|
});
|
|
@@ -2031,14 +2042,72 @@ var printAvailableCommands = () => {
|
|
|
2031
2042
|
console.log(chalk5.cyan(" nulljs host") + chalk5.gray(" - Set up production hosting with systemd"));
|
|
2032
2043
|
};
|
|
2033
2044
|
// src/commands/secret.ts
|
|
2034
|
-
init_api();
|
|
2035
|
-
init_config();
|
|
2036
2045
|
import { Command as Command2 } from "commander";
|
|
2037
2046
|
import chalk6 from "chalk";
|
|
2038
2047
|
import { resolve as resolve3 } from "path";
|
|
2039
2048
|
import { readFile, readdir as readdir3 } from "fs/promises";
|
|
2040
2049
|
import { existsSync as existsSync6 } from "fs";
|
|
2041
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();
|
|
2042
2111
|
var selectConfig2 = async () => {
|
|
2043
2112
|
const configList = listConfigs();
|
|
2044
2113
|
if (!configList || configList.configs.length === 0) {
|
|
@@ -2093,7 +2162,7 @@ var parseEnvFile = async (filePath) => {
|
|
|
2093
2162
|
const content = await readFile(filePath, "utf-8");
|
|
2094
2163
|
const lines = content.split(`
|
|
2095
2164
|
`);
|
|
2096
|
-
const
|
|
2165
|
+
const secrets = [];
|
|
2097
2166
|
for (const line of lines) {
|
|
2098
2167
|
const trimmed = line.trim();
|
|
2099
2168
|
if (!trimmed || trimmed.startsWith("#")) {
|
|
@@ -2109,13 +2178,13 @@ var parseEnvFile = async (filePath) => {
|
|
|
2109
2178
|
value = value.slice(1, -1);
|
|
2110
2179
|
}
|
|
2111
2180
|
if (key) {
|
|
2112
|
-
|
|
2181
|
+
secrets.push({ key, value });
|
|
2113
2182
|
}
|
|
2114
2183
|
}
|
|
2115
|
-
return
|
|
2184
|
+
return secrets;
|
|
2116
2185
|
};
|
|
2117
2186
|
var registerSecretCommand = (program) => {
|
|
2118
|
-
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) => {
|
|
2119
2188
|
let config;
|
|
2120
2189
|
try {
|
|
2121
2190
|
config = options.env ? getConfig(options.env) : await selectConfig2();
|
|
@@ -2132,7 +2201,7 @@ var registerSecretCommand = (program) => {
|
|
|
2132
2201
|
try {
|
|
2133
2202
|
console.log(chalk6.gray(`Fetching secrets from ${config.api}...`));
|
|
2134
2203
|
const privateKey = await loadPrivateKey(config);
|
|
2135
|
-
const keys = await listSecrets({ privateKey, url: config.api });
|
|
2204
|
+
const keys = await listSecrets({ privateKey, url: config.api, useCurl: options.curl });
|
|
2136
2205
|
if (keys.length === 0) {
|
|
2137
2206
|
console.log(chalk6.yellow("No secrets found"));
|
|
2138
2207
|
return;
|
|
@@ -2146,7 +2215,7 @@ Secret keys:`));
|
|
|
2146
2215
|
console.error(chalk6.red("\u2717 Failed to list secrets:"), error instanceof Error ? error.message : String(error));
|
|
2147
2216
|
process.exit(1);
|
|
2148
2217
|
}
|
|
2149
|
-
})).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) => {
|
|
2150
2219
|
const config = options?.env ? getConfig(options.env) : await selectConfig2();
|
|
2151
2220
|
if (!config) {
|
|
2152
2221
|
console.error(chalk6.red("\u2717 No configuration found."));
|
|
@@ -2192,13 +2261,13 @@ Secret keys:`));
|
|
|
2192
2261
|
await new Promise((resolve4) => setImmediate(resolve4));
|
|
2193
2262
|
try {
|
|
2194
2263
|
const privateKey = await loadPrivateKey(config);
|
|
2195
|
-
await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api });
|
|
2264
|
+
await createSecret({ key: secretKey, value: secretValue }, { privateKey, url: config.api, useCurl: options?.curl });
|
|
2196
2265
|
console.log(chalk6.green("\u2713 Secret created:") + ` ${chalk6.cyan(secretKey)}`);
|
|
2197
2266
|
} catch (error) {
|
|
2198
2267
|
console.error(chalk6.red("\u2717 Failed to create secret:"), error instanceof Error ? error.message : error);
|
|
2199
2268
|
process.exit(1);
|
|
2200
2269
|
}
|
|
2201
|
-
})).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) => {
|
|
2202
2271
|
const config = options?.env ? getConfig(options.env) : await selectConfig2();
|
|
2203
2272
|
if (!config) {
|
|
2204
2273
|
console.error(chalk6.red("\u2717 No configuration found."));
|
|
@@ -2218,19 +2287,19 @@ Secret keys:`));
|
|
|
2218
2287
|
}
|
|
2219
2288
|
await new Promise((resolve4) => setImmediate(resolve4));
|
|
2220
2289
|
try {
|
|
2221
|
-
const
|
|
2222
|
-
if (
|
|
2290
|
+
const secrets = await parseEnvFile(filePath);
|
|
2291
|
+
if (secrets.length === 0) {
|
|
2223
2292
|
console.log(chalk6.yellow("No secrets found in file"));
|
|
2224
2293
|
return;
|
|
2225
2294
|
}
|
|
2226
2295
|
const privateKey = await loadPrivateKey(config);
|
|
2227
2296
|
let created = 0;
|
|
2228
2297
|
let failed = 0;
|
|
2229
|
-
console.log(chalk6.cyan(`Deploying ${
|
|
2298
|
+
console.log(chalk6.cyan(`Deploying ${secrets.length} secret(s) to ${config.name}...
|
|
2230
2299
|
`));
|
|
2231
|
-
for (const secret of
|
|
2300
|
+
for (const secret of secrets) {
|
|
2232
2301
|
try {
|
|
2233
|
-
await createSecret(secret, { privateKey, url: config.api });
|
|
2302
|
+
await createSecret(secret, { privateKey, url: config.api, useCurl: options?.curl });
|
|
2234
2303
|
console.log(chalk6.green("\u2713") + ` ${secret.key}`);
|
|
2235
2304
|
created++;
|
|
2236
2305
|
} catch (error) {
|
|
@@ -2248,7 +2317,7 @@ Secret keys:`));
|
|
|
2248
2317
|
console.error(chalk6.red("\u2717 Failed to deploy secrets:"), error instanceof Error ? error.message : error);
|
|
2249
2318
|
process.exit(1);
|
|
2250
2319
|
}
|
|
2251
|
-
})).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) => {
|
|
2252
2321
|
const config = options?.env ? getConfig(options.env) : await selectConfig2();
|
|
2253
2322
|
if (!config) {
|
|
2254
2323
|
console.error(chalk6.red("\u2717 No configuration found."));
|
|
@@ -2268,19 +2337,19 @@ Secret keys:`));
|
|
|
2268
2337
|
}
|
|
2269
2338
|
await new Promise((resolve4) => setImmediate(resolve4));
|
|
2270
2339
|
try {
|
|
2271
|
-
const
|
|
2272
|
-
if (
|
|
2340
|
+
const secrets = await parseEnvFile(filePath);
|
|
2341
|
+
if (secrets.length === 0) {
|
|
2273
2342
|
console.log(chalk6.yellow("No secrets found in file"));
|
|
2274
2343
|
return;
|
|
2275
2344
|
}
|
|
2276
2345
|
const privateKey = await loadPrivateKey(config);
|
|
2277
2346
|
let created = 0;
|
|
2278
2347
|
let failed = 0;
|
|
2279
|
-
console.log(chalk6.cyan(`Importing ${
|
|
2348
|
+
console.log(chalk6.cyan(`Importing ${secrets.length} secret(s) to ${config.name}...
|
|
2280
2349
|
`));
|
|
2281
|
-
for (const secret of
|
|
2350
|
+
for (const secret of secrets) {
|
|
2282
2351
|
try {
|
|
2283
|
-
await createSecret(secret, { privateKey, url: config.api });
|
|
2352
|
+
await createSecret(secret, { privateKey, url: config.api, useCurl: options?.curl });
|
|
2284
2353
|
console.log(chalk6.green("\u2713") + ` ${secret.key}`);
|
|
2285
2354
|
created++;
|
|
2286
2355
|
} catch (error) {
|
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"
|