@tothalex/nulljs 0.0.57 → 0.0.59

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 +156 -142
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -53,6 +53,10 @@ var buildUrl = (path, options) => {
53
53
  var init_deployments = () => {};
54
54
 
55
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;
56
60
  var getMimeType = (fileName) => {
57
61
  if (fileName.endsWith(".js"))
58
62
  return "application/javascript";
@@ -79,15 +83,45 @@ var getMimeType = (fileName) => {
79
83
  const dataString = sortedBlobs.map((b) => `${b.fileName}:${b.size}:${b.type}`).join("|");
80
84
  const sign = await crypto.subtle.sign("Ed25519", privateKey, Buffer.from(dataString));
81
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
+ }
82
107
  }, createFunctionDeployment = async (deployment2, options) => {
83
108
  const handler = deployment2.assets.find((asset) => asset.fileName === "handler.js");
84
109
  if (!handler) {
85
110
  throw new Error(`Handler not found for ${deployment2.name}`);
86
111
  }
87
- const { form, blobs } = createFormDataWithInfo([handler]);
112
+ const { blobs } = createFormDataWithInfo([handler]);
88
113
  const signature = await createSignature(blobs, options.privateKey);
89
114
  const baseUrl = options.url || "";
90
- const response = await fetch(`${baseUrl}/api/deployment`, {
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, {
91
125
  method: "POST",
92
126
  headers: {
93
127
  authorization: signature
@@ -99,10 +133,19 @@ var getMimeType = (fileName) => {
99
133
  throw new Error(`Deployment failed (${response.status}): ${errorText}`);
100
134
  }
101
135
  }, createReactDeployment = async (deployment2, options) => {
102
- const { form, blobs } = createFormDataWithInfo(deployment2.assets);
136
+ const { blobs } = createFormDataWithInfo(deployment2.assets);
103
137
  const signature = await createSignature(blobs, options.privateKey);
104
138
  const baseUrl = options.url || "";
105
- const response = await fetch(`${baseUrl}/api/deployment/react`, {
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, {
106
149
  method: "POST",
107
150
  headers: {
108
151
  authorization: signature
@@ -114,6 +157,7 @@ var getMimeType = (fileName) => {
114
157
  throw new Error(`Deployment failed (${response.status}): ${errorText}`);
115
158
  }
116
159
  };
160
+ var init_deploy = () => {};
117
161
 
118
162
  // ../../packages/api/actions/invocations.ts
119
163
  var init_invocations = () => {};
@@ -195,6 +239,7 @@ var createSignatureHeader = async (privateKey, props) => {
195
239
  // ../../packages/api/actions/index.ts
196
240
  var init_actions = __esm(() => {
197
241
  init_deployments();
242
+ init_deploy();
198
243
  init_invocations();
199
244
  init_logs();
200
245
  init_assets();
@@ -208,10 +253,10 @@ var init_api = __esm(() => {
208
253
  });
209
254
 
210
255
  // src/config/index.ts
211
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
212
- import { join } from "path";
256
+ import { readFileSync, writeFileSync as writeFileSync2, existsSync, mkdirSync } from "fs";
257
+ import { join as join2 } from "path";
213
258
  import chalk from "chalk";
214
- var getLocalConfigDir = () => join(process.cwd(), ".nulljs"), getLocalConfigFile = () => join(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
259
+ var getLocalConfigDir = () => join2(process.cwd(), ".nulljs"), getLocalConfigFile = () => join2(getLocalConfigDir(), "config.json"), ensureLocalConfigDir = () => {
215
260
  const localDir = getLocalConfigDir();
216
261
  if (!existsSync(localDir)) {
217
262
  mkdirSync(localDir, { recursive: true });
@@ -229,7 +274,7 @@ var getLocalConfigDir = () => join(process.cwd(), ".nulljs"), getLocalConfigFile
229
274
  }
230
275
  }, writeConfigStore = (store) => {
231
276
  ensureLocalConfigDir();
232
- writeFileSync(getLocalConfigFile(), JSON.stringify(store, null, 2));
277
+ writeFileSync2(getLocalConfigFile(), JSON.stringify(store, null, 2));
233
278
  }, readLocalConfig = () => {
234
279
  const store = readConfigStore();
235
280
  if (!store)
@@ -328,14 +373,14 @@ var init_config = () => {};
328
373
 
329
374
  // src/lib/server.ts
330
375
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, realpathSync } from "fs";
331
- import { join as join2, dirname } from "path";
376
+ import { join as join3, dirname } from "path";
332
377
  import chalk2 from "chalk";
333
378
  var CLI_DIR, PLATFORMS, getPlatformKey = () => {
334
379
  return `${process.platform}-${process.arch}`;
335
380
  }, findProjectRoot = (startPath = process.cwd()) => {
336
381
  let currentPath = startPath;
337
382
  while (currentPath !== dirname(currentPath)) {
338
- if (existsSync2(join2(currentPath, "package.json"))) {
383
+ if (existsSync2(join3(currentPath, "package.json"))) {
339
384
  return currentPath;
340
385
  }
341
386
  currentPath = dirname(currentPath);
@@ -344,7 +389,7 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
344
389
  }, findMonorepoRoot = (startPath = process.cwd()) => {
345
390
  let currentPath = startPath;
346
391
  while (currentPath !== dirname(currentPath)) {
347
- if (existsSync2(join2(currentPath, "Cargo.toml")) && existsSync2(join2(currentPath, "package.json"))) {
392
+ if (existsSync2(join3(currentPath, "Cargo.toml")) && existsSync2(join3(currentPath, "package.json"))) {
348
393
  return currentPath;
349
394
  }
350
395
  currentPath = dirname(currentPath);
@@ -357,11 +402,11 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
357
402
  }
358
403
  const monorepoRoot = findMonorepoRoot() || findMonorepoRoot(CLI_DIR);
359
404
  if (monorepoRoot) {
360
- const debugBinary = join2(monorepoRoot, "target", "debug", "server");
405
+ const debugBinary = join3(monorepoRoot, "target", "debug", "server");
361
406
  if (existsSync2(debugBinary)) {
362
407
  return { path: debugBinary, source: "local-debug" };
363
408
  }
364
- const releaseBinary = join2(monorepoRoot, "target", "release", "server");
409
+ const releaseBinary = join3(monorepoRoot, "target", "release", "server");
365
410
  if (existsSync2(releaseBinary)) {
366
411
  return { path: releaseBinary, source: "local-release" };
367
412
  }
@@ -384,7 +429,7 @@ var CLI_DIR, PLATFORMS, getPlatformKey = () => {
384
429
  args.push("--api-port", "3000");
385
430
  args.push("--gateway-port", "3001");
386
431
  const projectRoot = findProjectRoot();
387
- const cloudPath = join2(projectRoot, ".nulljs");
432
+ const cloudPath = join3(projectRoot, ".nulljs");
388
433
  args.push("--cloud-path", cloudPath);
389
434
  if (!existsSync2(cloudPath)) {
390
435
  mkdirSync2(cloudPath, { recursive: true });
@@ -440,69 +485,7 @@ var init_server = __esm(() => {
440
485
  import { basename, extname } from "path";
441
486
  import react from "@vitejs/plugin-react";
442
487
  import tailwindcss from "@tailwindcss/vite";
443
- var jsConfig = ({ filePath }) => {
444
- const entry = basename(filePath, extname(filePath));
445
- const virtualPrefix = `virtual:config/${entry}.tsx`;
446
- return {
447
- name: "nulljs-config-plugin",
448
- apply: "build",
449
- config: async (config, { command }) => {
450
- if (command !== "build") {
451
- return config;
452
- }
453
- return {
454
- build: {
455
- ssr: true,
456
- rollupOptions: {
457
- input: {
458
- [entry]: virtualPrefix
459
- },
460
- external: ["react", "react/jsx-runtime"],
461
- output: {
462
- entryFileNames: "[name].js"
463
- },
464
- preserveEntrySignatures: "strict"
465
- }
466
- }
467
- };
468
- },
469
- resolveId: (id) => {
470
- if (id === virtualPrefix) {
471
- return id;
472
- }
473
- return null;
474
- },
475
- load: (id) => {
476
- if (id === virtualPrefix) {
477
- const script = `
478
- import { config } from "${filePath.replace(/\\/g, "\\\\")}";
479
-
480
- export default config;
481
- `;
482
- return script;
483
- }
484
- return null;
485
- },
486
- transform: (code, id) => {
487
- if (id === filePath) {
488
- const configMatch = code.match(/export const config = \{[^}]*\};/);
489
- return configMatch ? { code: configMatch[0], map: null } : null;
490
- }
491
- }
492
- };
493
- }, spaConfigConfig = (filePath) => {
494
- return {
495
- logLevel: "error",
496
- plugins: [
497
- jsConfig({
498
- filePath
499
- })
500
- ],
501
- build: {
502
- outDir: "/tmp"
503
- }
504
- };
505
- }, jsSpa = ({ filePath }) => {
488
+ var jsSpa = ({ filePath }) => {
506
489
  const entry = basename(filePath, extname(filePath));
507
490
  const virtualPrefix = `virtual:spa/${entry}.tsx`;
508
491
  return {
@@ -520,7 +503,9 @@ var jsConfig = ({ filePath }) => {
520
503
  },
521
504
  external: ["cloud"],
522
505
  output: {
523
- entryFileNames: "[name].js"
506
+ entryFileNames: "[name].js",
507
+ chunkFileNames: "[name]-[hash].js",
508
+ assetFileNames: "[name]-[hash][extname]"
524
509
  }
525
510
  }
526
511
  },
@@ -708,7 +693,7 @@ var deployedHashes, clearDeployCache = () => {
708
693
  deployedHashes.clear();
709
694
  }, hashCode = (code) => {
710
695
  return Bun.hash(code).toString(16);
711
- }, deployFunction = async (filePath, privateKey, apiUrl) => {
696
+ }, deployFunction = async (filePath, privateKey, apiUrl, useCurl) => {
712
697
  const fileName = basename2(filePath);
713
698
  const result = await build(functionConfig(filePath));
714
699
  const handler = result.output.find((output) => output.type === "chunk" && output.fileName === "handler.js");
@@ -720,19 +705,13 @@ var deployedHashes, clearDeployCache = () => {
720
705
  if (lastHash === hash) {
721
706
  return { deployed: false, skipped: true };
722
707
  }
723
- await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl });
708
+ await createFunctionDeployment({ name: fileName, assets: [{ fileName: "handler.js", code: handler.code }] }, { privateKey, url: apiUrl, useCurl });
724
709
  deployedHashes.set(filePath, hash);
725
710
  return { deployed: true, skipped: false };
726
- }, deployReact = async (filePath, privateKey, apiUrl) => {
711
+ }, deployReact = async (filePath, privateKey, apiUrl, useCurl) => {
727
712
  const fileName = basename2(filePath);
728
- const configResult = await build(spaConfigConfig(filePath));
729
- const configOutput = configResult.output.find((output) => output.type === "chunk" && output.fileName === "index.js");
730
- if (!configOutput) {
731
- throw new Error("Config build failed: config file not found in output");
732
- }
733
713
  const result = await build(spaClientConfig(filePath));
734
714
  const assets3 = [];
735
- assets3.push({ fileName: "config.js", code: configOutput.code });
736
715
  for (const output of result.output) {
737
716
  if (output.type === "chunk") {
738
717
  assets3.push({ fileName: output.fileName, code: output.code });
@@ -746,11 +725,11 @@ var deployedHashes, clearDeployCache = () => {
746
725
  if (lastHash === hash) {
747
726
  return { deployed: false, skipped: true };
748
727
  }
749
- await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl });
728
+ await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl, useCurl });
750
729
  deployedHashes.set(filePath, hash);
751
730
  return { deployed: true, skipped: false };
752
731
  };
753
- var init_deploy = __esm(() => {
732
+ var init_deploy2 = __esm(() => {
754
733
  init_api();
755
734
  init_bundle();
756
735
  deployedHashes = new Map;
@@ -759,19 +738,19 @@ var init_deploy = __esm(() => {
759
738
  // src/lib/watcher.ts
760
739
  import { watch, existsSync as existsSync3 } from "fs";
761
740
  import { readdir } from "fs/promises";
762
- import { join as join3 } from "path";
741
+ import { join as join4 } from "path";
763
742
  import { build as build2 } from "vite";
764
743
  var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
765
744
  return Bun.hash(code).toString(16);
766
745
  }, findFunctionEntries = async (functionsPath) => {
767
746
  const entries = [];
768
747
  for (const dir of FUNCTION_DIRS) {
769
- const dirPath = join3(functionsPath, dir);
748
+ const dirPath = join4(functionsPath, dir);
770
749
  try {
771
750
  const items = await readdir(dirPath, { withFileTypes: true });
772
751
  for (const item of items) {
773
752
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
774
- const fullPath = join3(dirPath, item.name);
753
+ const fullPath = join4(dirPath, item.name);
775
754
  const name = `${dir}/${item.name.replace(/\.tsx?$/, "")}`;
776
755
  entries.push({
777
756
  name,
@@ -791,7 +770,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
791
770
  console.error('No local configuration found. Run "nulljs dev" first.');
792
771
  return () => {};
793
772
  }
794
- const functionsPath = join3(srcPath, "function");
773
+ const functionsPath = join4(srcPath, "function");
795
774
  let entries = await findFunctionEntries(functionsPath);
796
775
  let viteWatcher = null;
797
776
  let isRestarting = false;
@@ -862,7 +841,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
862
841
  viteWatcher = await startViteWatcher();
863
842
  const dirWatchers = [];
864
843
  for (const dir of FUNCTION_DIRS) {
865
- const dirPath = join3(functionsPath, dir);
844
+ const dirPath = join4(functionsPath, dir);
866
845
  if (!existsSync3(dirPath))
867
846
  continue;
868
847
  try {
@@ -871,7 +850,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
871
850
  return;
872
851
  if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
873
852
  return;
874
- const fullPath = join3(dirPath, filename);
853
+ const fullPath = join4(dirPath, filename);
875
854
  const entryName = `${dir}/${filename.replace(/\.tsx?$/, "")}`;
876
855
  const existingEntry = entries.find((e) => e.name === entryName);
877
856
  const fileExists = existsSync3(fullPath);
@@ -898,14 +877,14 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
898
877
  }
899
878
  const privateKey = await loadPrivateKey(config);
900
879
  deployedHashes2.clear();
901
- const functionsPath = join3(srcPath, "function");
880
+ const functionsPath = join4(srcPath, "function");
902
881
  const entries = await findFunctionEntries(functionsPath);
903
- const reactEntry = join3(srcPath, "index.tsx");
882
+ const reactEntry = join4(srcPath, "index.tsx");
904
883
  const hasReact = existsSync3(reactEntry);
905
884
  let total = entries.length + (hasReact ? 1 : 0);
906
885
  let successful = 0;
907
886
  let failed = 0;
908
- const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (init_deploy(), exports_deploy));
887
+ const { deployFunction: deployFunction2, deployReact: deployReact2, clearDeployCache: clearDeployCache2 } = await Promise.resolve().then(() => (init_deploy2(), exports_deploy));
909
888
  clearDeployCache2();
910
889
  for (const entry of entries) {
911
890
  try {
@@ -1751,9 +1730,9 @@ var registerDevCommand = (program) => {
1751
1730
  // src/commands/deploy.ts
1752
1731
  init_config();
1753
1732
  init_bundle();
1754
- init_deploy();
1733
+ init_deploy2();
1755
1734
  import chalk3 from "chalk";
1756
- import { basename as basename4, resolve as resolve2, join as join4 } from "path";
1735
+ import { basename as basename4, resolve as resolve2, join as join5 } from "path";
1757
1736
  import { existsSync as existsSync5 } from "fs";
1758
1737
  import { readdir as readdir2 } from "fs/promises";
1759
1738
  import * as p from "@clack/prompts";
@@ -1783,24 +1762,24 @@ var selectConfig = async () => {
1783
1762
  };
1784
1763
  var findAllDeployables = async (srcPath) => {
1785
1764
  const functions = [];
1786
- const functionsPath = join4(srcPath, "function");
1765
+ const functionsPath = join5(srcPath, "function");
1787
1766
  for (const dir of FUNCTION_DIRS2) {
1788
- const dirPath = join4(functionsPath, dir);
1767
+ const dirPath = join5(functionsPath, dir);
1789
1768
  try {
1790
1769
  const items = await readdir2(dirPath, { withFileTypes: true });
1791
1770
  for (const item of items) {
1792
1771
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
1793
- functions.push(join4(dirPath, item.name));
1772
+ functions.push(join5(dirPath, item.name));
1794
1773
  }
1795
1774
  }
1796
1775
  } catch {}
1797
1776
  }
1798
- const reactEntry = join4(srcPath, "index.tsx");
1777
+ const reactEntry = join5(srcPath, "index.tsx");
1799
1778
  const hasReact = existsSync5(reactEntry);
1800
1779
  return { functions, reactEntry: hasReact ? reactEntry : null };
1801
1780
  };
1802
1781
  var deployAll = async (config, options) => {
1803
- const srcPath = join4(process.cwd(), "src");
1782
+ const srcPath = join5(process.cwd(), "src");
1804
1783
  if (!existsSync5(srcPath)) {
1805
1784
  console.error(chalk3.red("\u2717 No src directory found"));
1806
1785
  process.exit(1);
@@ -1824,7 +1803,7 @@ var deployAll = async (config, options) => {
1824
1803
  const fileName = basename4(filePath);
1825
1804
  try {
1826
1805
  console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
1827
- const result = await deployFunction(filePath, privateKey, config.api);
1806
+ const result = await deployFunction(filePath, privateKey, config.api, options.curl);
1828
1807
  if (result.skipped) {
1829
1808
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1830
1809
  skipped++;
@@ -1841,7 +1820,7 @@ var deployAll = async (config, options) => {
1841
1820
  const fileName = basename4(reactEntry);
1842
1821
  try {
1843
1822
  console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
1844
- const result = await deployReact(reactEntry, privateKey, config.api);
1823
+ const result = await deployReact(reactEntry, privateKey, config.api, options.curl);
1845
1824
  if (result.skipped) {
1846
1825
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1847
1826
  skipped++;
@@ -1863,7 +1842,7 @@ var deployAll = async (config, options) => {
1863
1842
  }
1864
1843
  };
1865
1844
  var registerDeployCommand = (program) => {
1866
- program.command("deploy").description("Bundle and deploy functions and React SPAs").argument("[file]", "Path to a specific file (deploys all if omitted)").option("-e, --env <name>", "Use a specific config environment").option("-f, --force", "Force deploy even if unchanged").action(async (file, options) => {
1845
+ 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) => {
1867
1846
  let config;
1868
1847
  if (options.env) {
1869
1848
  config = getConfig(options.env);
@@ -1897,7 +1876,7 @@ var registerDeployCommand = (program) => {
1897
1876
  try {
1898
1877
  if (isReact(filePath)) {
1899
1878
  console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
1900
- const result = await deployReact(filePath, privateKey, config.api);
1879
+ const result = await deployReact(filePath, privateKey, config.api, options.curl);
1901
1880
  if (result.skipped) {
1902
1881
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1903
1882
  } else {
@@ -1905,7 +1884,7 @@ var registerDeployCommand = (program) => {
1905
1884
  }
1906
1885
  } else {
1907
1886
  console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
1908
- const result = await deployFunction(filePath, privateKey, config.api);
1887
+ const result = await deployFunction(filePath, privateKey, config.api, options.curl);
1909
1888
  if (result.skipped) {
1910
1889
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1911
1890
  } else {
@@ -2322,10 +2301,10 @@ Secret keys:`));
2322
2301
  }));
2323
2302
  };
2324
2303
  // src/commands/host.ts
2325
- import { existsSync as existsSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2326
- import { join as join5 } from "path";
2327
- import { execSync } from "child_process";
2304
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2305
+ import { join as join6 } from "path";
2328
2306
  import { homedir } from "os";
2307
+ var {$: $2 } = globalThis.Bun;
2329
2308
  import chalk7 from "chalk";
2330
2309
  var PLATFORMS2 = {
2331
2310
  "linux-x64": "@tothalex/nulljs-linux-x64",
@@ -2355,8 +2334,8 @@ var generateProductionKeys = async () => {
2355
2334
  const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)));
2356
2335
  return { privateKey, publicKey };
2357
2336
  };
2358
- var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
2359
- const configPath = join5(cloudPath, "config.json");
2337
+ var saveProductionConfig = async (cloudPath, privateKey, publicKey) => {
2338
+ const configPath = join6(cloudPath, "config.json");
2360
2339
  const config = {
2361
2340
  key: {
2362
2341
  private: privateKey,
@@ -2364,8 +2343,8 @@ var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
2364
2343
  }
2365
2344
  };
2366
2345
  console.log(chalk7.blue("Saving production configuration..."));
2367
- writeFileSync2(configPath, JSON.stringify(config, null, 2));
2368
- execSync(`chmod 600 ${configPath}`, { stdio: "inherit" });
2346
+ writeFileSync3(configPath, JSON.stringify(config, null, 2));
2347
+ await $2`chmod 600 ${configPath}`;
2369
2348
  console.log(chalk7.green("Production configuration saved"));
2370
2349
  };
2371
2350
  var createSystemdService = (cloudPath, publicKey, serverBinPath, userName) => {
@@ -2403,14 +2382,15 @@ var ensureCloudDirectory = (cloudPath) => {
2403
2382
  console.log(chalk7.gray(`Cloud directory already exists: ${cloudPath}`));
2404
2383
  }
2405
2384
  };
2406
- var installSystemdService = (serviceContent) => {
2385
+ var installSystemdService = async (serviceContent) => {
2407
2386
  const servicePath = "/etc/systemd/system/nulljs.service";
2408
2387
  console.log(chalk7.blue("Installing systemd service..."));
2409
- writeFileSync2("/tmp/nulljs.service", serviceContent);
2410
- execSync(`sudo mv /tmp/nulljs.service ${servicePath}`, { stdio: "inherit" });
2411
- execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
2412
- execSync("sudo systemctl enable nulljs.service", { stdio: "inherit" });
2413
- console.log(chalk7.green("Systemd service installed and enabled"));
2388
+ writeFileSync3("/tmp/nulljs.service", serviceContent);
2389
+ await $2`sudo mv /tmp/nulljs.service ${servicePath}`;
2390
+ await $2`sudo systemctl daemon-reload`;
2391
+ await $2`sudo systemctl enable nulljs.service`;
2392
+ await $2`sudo systemctl start nulljs.service`;
2393
+ console.log(chalk7.green("Systemd service installed, enabled and started"));
2414
2394
  };
2415
2395
  var validateLinux = () => {
2416
2396
  if (process.platform !== "linux") {
@@ -2426,7 +2406,7 @@ var validateServerBinary = (serverBinPath) => {
2426
2406
  }
2427
2407
  };
2428
2408
  var checkExistingProductionConfig = (cloudPath) => {
2429
- const configPath = join5(cloudPath, "config.json");
2409
+ const configPath = join6(cloudPath, "config.json");
2430
2410
  if (!existsSync7(configPath)) {
2431
2411
  return null;
2432
2412
  }
@@ -2446,13 +2426,13 @@ var unhost = async (options = {}) => {
2446
2426
  try {
2447
2427
  console.log(chalk7.blue("Stopping NullJS service..."));
2448
2428
  try {
2449
- execSync("sudo systemctl stop nulljs", { stdio: "inherit" });
2429
+ await $2`sudo systemctl stop nulljs`;
2450
2430
  console.log(chalk7.green("Service stopped"));
2451
2431
  } catch {
2452
2432
  console.log(chalk7.yellow("Service was not running"));
2453
2433
  }
2454
2434
  try {
2455
- execSync("sudo systemctl disable nulljs", { stdio: "inherit" });
2435
+ await $2`sudo systemctl disable nulljs`;
2456
2436
  console.log(chalk7.green("Service disabled"));
2457
2437
  } catch {
2458
2438
  console.log(chalk7.yellow("Service was not enabled"));
@@ -2460,15 +2440,15 @@ var unhost = async (options = {}) => {
2460
2440
  const servicePath = "/etc/systemd/system/nulljs.service";
2461
2441
  if (existsSync7(servicePath)) {
2462
2442
  console.log(chalk7.blue("Removing systemd service file..."));
2463
- execSync(`sudo rm ${servicePath}`, { stdio: "inherit" });
2464
- execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
2443
+ await $2`sudo rm ${servicePath}`;
2444
+ await $2`sudo systemctl daemon-reload`;
2465
2445
  console.log(chalk7.green("Systemd service file removed"));
2466
2446
  }
2467
2447
  if (!options.keepData) {
2468
- const defaultCloudPath = join5(homedir(), ".nulljs");
2448
+ const defaultCloudPath = join6(homedir(), ".nulljs");
2469
2449
  if (existsSync7(defaultCloudPath)) {
2470
2450
  console.log(chalk7.blue("Removing cloud directory..."));
2471
- execSync(`rm -rf ${defaultCloudPath}`, { stdio: "inherit" });
2451
+ await $2`rm -rf ${defaultCloudPath}`;
2472
2452
  console.log(chalk7.green("Cloud directory removed"));
2473
2453
  } else {
2474
2454
  console.log(chalk7.gray("Cloud directory does not exist"));
@@ -2488,9 +2468,42 @@ var unhost = async (options = {}) => {
2488
2468
  process.exit(1);
2489
2469
  }
2490
2470
  };
2471
+ var update = async () => {
2472
+ validateLinux();
2473
+ const cloudPath = join6(homedir(), ".nulljs");
2474
+ const servicePath = "/etc/systemd/system/nulljs.service";
2475
+ if (!existsSync7(servicePath)) {
2476
+ console.log(chalk7.red("NullJS service is not installed."));
2477
+ console.log(chalk7.gray('Run "nulljs host" to set up hosting first.'));
2478
+ process.exit(1);
2479
+ }
2480
+ const productionConfig = checkExistingProductionConfig(cloudPath);
2481
+ if (!productionConfig) {
2482
+ console.log(chalk7.red("Production config not found."));
2483
+ console.log(chalk7.gray('Run "nulljs host" to set up hosting first.'));
2484
+ process.exit(1);
2485
+ }
2486
+ console.log(chalk7.blue("Updating NullJS service..."));
2487
+ console.log(chalk7.blue("Stopping service..."));
2488
+ try {
2489
+ await $2`sudo systemctl stop nulljs`;
2490
+ } catch {}
2491
+ const serverBinPath = getServerBinPath();
2492
+ validateServerBinary(serverBinPath);
2493
+ console.log(chalk7.gray(` Binary path: ${serverBinPath}`));
2494
+ const currentUser = process.env.USER || process.env.USERNAME || "root";
2495
+ const serviceContent = createSystemdService(cloudPath, productionConfig.key.public, serverBinPath, currentUser);
2496
+ console.log(chalk7.blue("Updating systemd service..."));
2497
+ writeFileSync3("/tmp/nulljs.service", serviceContent);
2498
+ await $2`sudo mv /tmp/nulljs.service ${servicePath}`;
2499
+ await $2`sudo systemctl daemon-reload`;
2500
+ console.log(chalk7.blue("Starting service..."));
2501
+ await $2`sudo systemctl start nulljs.service`;
2502
+ console.log(chalk7.green("NullJS service updated and restarted!"));
2503
+ };
2491
2504
  var host = async (cloudPath) => {
2492
2505
  validateLinux();
2493
- const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath : join5(process.cwd(), cloudPath) : join5(homedir(), ".nulljs");
2506
+ const resolvedCloudPath = cloudPath ? cloudPath.startsWith("/") ? cloudPath : join6(process.cwd(), cloudPath) : join6(homedir(), ".nulljs");
2494
2507
  console.log(chalk7.blue("Setting up NullJS production hosting..."));
2495
2508
  console.log(chalk7.gray(` Cloud path: ${resolvedCloudPath}`));
2496
2509
  const serverBinPath = getServerBinPath();
@@ -2501,7 +2514,7 @@ var host = async (cloudPath) => {
2501
2514
  let productionConfig = checkExistingProductionConfig(resolvedCloudPath);
2502
2515
  if (!productionConfig) {
2503
2516
  const { privateKey, publicKey } = await generateProductionKeys();
2504
- saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
2517
+ await saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
2505
2518
  productionConfig = { key: { private: privateKey, public: publicKey } };
2506
2519
  console.log(chalk7.green("Production keys generated"));
2507
2520
  console.log(chalk7.blue("Production Public Key:"));
@@ -2512,32 +2525,33 @@ var host = async (cloudPath) => {
2512
2525
  console.log(chalk7.gray(productionConfig.key.public));
2513
2526
  }
2514
2527
  const serviceContent = createSystemdService(resolvedCloudPath, productionConfig.key.public, serverBinPath, currentUser);
2515
- installSystemdService(serviceContent);
2528
+ await installSystemdService(serviceContent);
2516
2529
  console.log(chalk7.green("NullJS hosting setup complete!"));
2517
2530
  console.log("");
2518
2531
  console.log(chalk7.blue("Service management commands:"));
2519
- console.log(chalk7.gray(" Start: "), chalk7.white("sudo systemctl start nulljs"));
2520
2532
  console.log(chalk7.gray(" Stop: "), chalk7.white("sudo systemctl stop nulljs"));
2533
+ console.log(chalk7.gray(" Restart: "), chalk7.white("sudo systemctl restart nulljs"));
2521
2534
  console.log(chalk7.gray(" Status: "), chalk7.white("sudo systemctl status nulljs"));
2522
2535
  console.log(chalk7.gray(" Logs: "), chalk7.white("sudo journalctl -u nulljs -f"));
2523
- console.log("");
2524
- console.log(chalk7.yellow("Remember to start the service: sudo systemctl start nulljs"));
2525
2536
  };
2526
2537
  var registerHostCommand = (program) => {
2527
2538
  const hostCmd = program.command("host").description("Set up NullJS production hosting with systemd").argument("[cloud-path]", "Path for cloud storage (default: ~/.nulljs)").action(async (cloudPath) => {
2528
2539
  await host(cloudPath);
2529
2540
  });
2541
+ hostCmd.command("update").description("Update NullJS service after package upgrade").action(async () => {
2542
+ await update();
2543
+ });
2530
2544
  hostCmd.command("remove").description("Remove NullJS production hosting").option("--keep-data", "Keep cloud data directory").action(async (options) => {
2531
2545
  await unhost(options);
2532
2546
  });
2533
- hostCmd.command("logs").description("View NullJS server logs").option("-n, --lines <number>", "Number of lines to show", "100").option("--no-follow", "Do not follow log output").action((options) => {
2547
+ 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) => {
2534
2548
  validateLinux();
2535
- const args = ["-u", "nulljs", "-n", options.lines];
2536
- if (options.follow) {
2537
- args.push("-f");
2538
- }
2539
2549
  try {
2540
- execSync(`journalctl ${args.join(" ")}`, { stdio: "inherit" });
2550
+ if (options.follow) {
2551
+ await $2`journalctl -u nulljs -n ${options.lines} -f`;
2552
+ } else {
2553
+ await $2`journalctl -u nulljs -n ${options.lines}`;
2554
+ }
2541
2555
  } catch {}
2542
2556
  });
2543
2557
  };
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.57",
4
+ "version": "0.0.59",
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.81",
22
+ "@tothalex/nulljs-linux-arm64": "^0.0.81",
23
+ "@tothalex/nulljs-linux-x64": "^0.0.81"
24
24
  },
25
25
  "files": [
26
26
  "dist"