@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.
Files changed (2) hide show
  1. package/dist/cli.js +351 -202
  2. 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 init_deploy = __esm(() => {
688
- init_api();
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 join3 } from "path";
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 = join3(functionsPath, dir);
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 = join3(dirPath, item.name);
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 = join3(srcPath, "function");
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 = join3(functionsPath, dir);
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 = join3(dirPath, filename);
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 = join3(srcPath, "function");
891
+ const functionsPath = join4(srcPath, "function");
836
892
  const entries = await findFunctionEntries(functionsPath);
837
- const reactEntry = join3(srcPath, "index.tsx");
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(() => (init_deploy(), exports_deploy));
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
- init_api();
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
- init_deploy();
1744
+ init_deploy2();
1689
1745
  import chalk3 from "chalk";
1690
- import { basename as basename4, resolve as resolve2, join as join4 } from "path";
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 = join4(srcPath, "function");
1776
+ const functionsPath = join5(srcPath, "function");
1721
1777
  for (const dir of FUNCTION_DIRS2) {
1722
- const dirPath = join4(functionsPath, dir);
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(join4(dirPath, item.name));
1783
+ functions.push(join5(dirPath, item.name));
1728
1784
  }
1729
1785
  }
1730
1786
  } catch {}
1731
1787
  }
1732
- const reactEntry = join4(srcPath, "index.tsx");
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 = join4(process.cwd(), "src");
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 secrets2 = [];
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
- secrets2.push({ key, value });
2181
+ secrets.push({ key, value });
2068
2182
  }
2069
2183
  }
2070
- return secrets2;
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 secrets2 = await parseEnvFile(filePath);
2177
- if (secrets2.length === 0) {
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 ${secrets2.length} secret(s) to ${config.name}...
2298
+ console.log(chalk6.cyan(`Deploying ${secrets.length} secret(s) to ${config.name}...
2185
2299
  `));
2186
- for (const secret of secrets2) {
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 secrets2 = await parseEnvFile(filePath);
2227
- if (secrets2.length === 0) {
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 ${secrets2.length} secret(s) to ${config.name}...
2348
+ console.log(chalk6.cyan(`Importing ${secrets.length} secret(s) to ${config.name}...
2235
2349
  `));
2236
- for (const secret of secrets2) {
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 writeFileSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2260
- import { join as join5 } from "path";
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 = join5(cloudPath, "config.json");
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
- writeFileSync2(configPath, JSON.stringify(config, null, 2));
2302
- execSync(`chmod 600 ${configPath}`, { stdio: "inherit" });
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
- writeFileSync2("/tmp/nulljs.service", serviceContent);
2344
- execSync(`sudo mv /tmp/nulljs.service ${servicePath}`, { stdio: "inherit" });
2345
- execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
2346
- execSync("sudo systemctl enable nulljs.service", { stdio: "inherit" });
2347
- console.log(chalk7.green("Systemd service installed and enabled"));
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 = join5(cloudPath, "config.json");
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
- execSync("sudo systemctl stop nulljs", { stdio: "inherit" });
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
- execSync("sudo systemctl disable nulljs", { stdio: "inherit" });
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
- execSync(`sudo rm ${servicePath}`, { stdio: "inherit" });
2398
- execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
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 = join5(homedir(), ".nulljs");
2517
+ const defaultCloudPath = join6(homedir(), ".nulljs");
2403
2518
  if (existsSync7(defaultCloudPath)) {
2404
2519
  console.log(chalk7.blue("Removing cloud directory..."));
2405
- execSync(`rm -rf ${defaultCloudPath}`, { stdio: "inherit" });
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 : join5(process.cwd(), cloudPath) : join5(homedir(), ".nulljs");
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
- execSync(`journalctl ${args.join(" ")}`, { stdio: "inherit" });
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.58",
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.79",
22
- "@tothalex/nulljs-linux-arm64": "^0.0.79",
23
- "@tothalex/nulljs-linux-x64": "^0.0.79"
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"