@tothalex/nulljs 0.0.58 → 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 +152 -72
  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 });
@@ -648,7 +693,7 @@ var deployedHashes, clearDeployCache = () => {
648
693
  deployedHashes.clear();
649
694
  }, hashCode = (code) => {
650
695
  return Bun.hash(code).toString(16);
651
- }, deployFunction = async (filePath, privateKey, apiUrl) => {
696
+ }, deployFunction = async (filePath, privateKey, apiUrl, useCurl) => {
652
697
  const fileName = basename2(filePath);
653
698
  const result = await build(functionConfig(filePath));
654
699
  const handler = result.output.find((output) => output.type === "chunk" && output.fileName === "handler.js");
@@ -660,10 +705,10 @@ var deployedHashes, clearDeployCache = () => {
660
705
  if (lastHash === hash) {
661
706
  return { deployed: false, skipped: true };
662
707
  }
663
- 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 });
664
709
  deployedHashes.set(filePath, hash);
665
710
  return { deployed: true, skipped: false };
666
- }, deployReact = async (filePath, privateKey, apiUrl) => {
711
+ }, deployReact = async (filePath, privateKey, apiUrl, useCurl) => {
667
712
  const fileName = basename2(filePath);
668
713
  const result = await build(spaClientConfig(filePath));
669
714
  const assets3 = [];
@@ -680,11 +725,11 @@ var deployedHashes, clearDeployCache = () => {
680
725
  if (lastHash === hash) {
681
726
  return { deployed: false, skipped: true };
682
727
  }
683
- await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl });
728
+ await createReactDeployment({ name: fileName, assets: assets3 }, { privateKey, url: apiUrl, useCurl });
684
729
  deployedHashes.set(filePath, hash);
685
730
  return { deployed: true, skipped: false };
686
731
  };
687
- var init_deploy = __esm(() => {
732
+ var init_deploy2 = __esm(() => {
688
733
  init_api();
689
734
  init_bundle();
690
735
  deployedHashes = new Map;
@@ -693,19 +738,19 @@ var init_deploy = __esm(() => {
693
738
  // src/lib/watcher.ts
694
739
  import { watch, existsSync as existsSync3 } from "fs";
695
740
  import { readdir } from "fs/promises";
696
- import { join as join3 } from "path";
741
+ import { join as join4 } from "path";
697
742
  import { build as build2 } from "vite";
698
743
  var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
699
744
  return Bun.hash(code).toString(16);
700
745
  }, findFunctionEntries = async (functionsPath) => {
701
746
  const entries = [];
702
747
  for (const dir of FUNCTION_DIRS) {
703
- const dirPath = join3(functionsPath, dir);
748
+ const dirPath = join4(functionsPath, dir);
704
749
  try {
705
750
  const items = await readdir(dirPath, { withFileTypes: true });
706
751
  for (const item of items) {
707
752
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
708
- const fullPath = join3(dirPath, item.name);
753
+ const fullPath = join4(dirPath, item.name);
709
754
  const name = `${dir}/${item.name.replace(/\.tsx?$/, "")}`;
710
755
  entries.push({
711
756
  name,
@@ -725,7 +770,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
725
770
  console.error('No local configuration found. Run "nulljs dev" first.');
726
771
  return () => {};
727
772
  }
728
- const functionsPath = join3(srcPath, "function");
773
+ const functionsPath = join4(srcPath, "function");
729
774
  let entries = await findFunctionEntries(functionsPath);
730
775
  let viteWatcher = null;
731
776
  let isRestarting = false;
@@ -796,7 +841,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
796
841
  viteWatcher = await startViteWatcher();
797
842
  const dirWatchers = [];
798
843
  for (const dir of FUNCTION_DIRS) {
799
- const dirPath = join3(functionsPath, dir);
844
+ const dirPath = join4(functionsPath, dir);
800
845
  if (!existsSync3(dirPath))
801
846
  continue;
802
847
  try {
@@ -805,7 +850,7 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
805
850
  return;
806
851
  if (!filename.endsWith(".ts") && !filename.endsWith(".tsx"))
807
852
  return;
808
- const fullPath = join3(dirPath, filename);
853
+ const fullPath = join4(dirPath, filename);
809
854
  const entryName = `${dir}/${filename.replace(/\.tsx?$/, "")}`;
810
855
  const existingEntry = entries.find((e) => e.name === entryName);
811
856
  const fileExists = existsSync3(fullPath);
@@ -832,14 +877,14 @@ var FUNCTION_DIRS, deployedHashes2, hashCode2 = (code) => {
832
877
  }
833
878
  const privateKey = await loadPrivateKey(config);
834
879
  deployedHashes2.clear();
835
- const functionsPath = join3(srcPath, "function");
880
+ const functionsPath = join4(srcPath, "function");
836
881
  const entries = await findFunctionEntries(functionsPath);
837
- const reactEntry = join3(srcPath, "index.tsx");
882
+ const reactEntry = join4(srcPath, "index.tsx");
838
883
  const hasReact = existsSync3(reactEntry);
839
884
  let total = entries.length + (hasReact ? 1 : 0);
840
885
  let successful = 0;
841
886
  let failed = 0;
842
- 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));
843
888
  clearDeployCache2();
844
889
  for (const entry of entries) {
845
890
  try {
@@ -1685,9 +1730,9 @@ var registerDevCommand = (program) => {
1685
1730
  // src/commands/deploy.ts
1686
1731
  init_config();
1687
1732
  init_bundle();
1688
- init_deploy();
1733
+ init_deploy2();
1689
1734
  import chalk3 from "chalk";
1690
- 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";
1691
1736
  import { existsSync as existsSync5 } from "fs";
1692
1737
  import { readdir as readdir2 } from "fs/promises";
1693
1738
  import * as p from "@clack/prompts";
@@ -1717,24 +1762,24 @@ var selectConfig = async () => {
1717
1762
  };
1718
1763
  var findAllDeployables = async (srcPath) => {
1719
1764
  const functions = [];
1720
- const functionsPath = join4(srcPath, "function");
1765
+ const functionsPath = join5(srcPath, "function");
1721
1766
  for (const dir of FUNCTION_DIRS2) {
1722
- const dirPath = join4(functionsPath, dir);
1767
+ const dirPath = join5(functionsPath, dir);
1723
1768
  try {
1724
1769
  const items = await readdir2(dirPath, { withFileTypes: true });
1725
1770
  for (const item of items) {
1726
1771
  if (item.isFile() && (item.name.endsWith(".ts") || item.name.endsWith(".tsx"))) {
1727
- functions.push(join4(dirPath, item.name));
1772
+ functions.push(join5(dirPath, item.name));
1728
1773
  }
1729
1774
  }
1730
1775
  } catch {}
1731
1776
  }
1732
- const reactEntry = join4(srcPath, "index.tsx");
1777
+ const reactEntry = join5(srcPath, "index.tsx");
1733
1778
  const hasReact = existsSync5(reactEntry);
1734
1779
  return { functions, reactEntry: hasReact ? reactEntry : null };
1735
1780
  };
1736
1781
  var deployAll = async (config, options) => {
1737
- const srcPath = join4(process.cwd(), "src");
1782
+ const srcPath = join5(process.cwd(), "src");
1738
1783
  if (!existsSync5(srcPath)) {
1739
1784
  console.error(chalk3.red("\u2717 No src directory found"));
1740
1785
  process.exit(1);
@@ -1758,7 +1803,7 @@ var deployAll = async (config, options) => {
1758
1803
  const fileName = basename4(filePath);
1759
1804
  try {
1760
1805
  console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
1761
- const result = await deployFunction(filePath, privateKey, config.api);
1806
+ const result = await deployFunction(filePath, privateKey, config.api, options.curl);
1762
1807
  if (result.skipped) {
1763
1808
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1764
1809
  skipped++;
@@ -1775,7 +1820,7 @@ var deployAll = async (config, options) => {
1775
1820
  const fileName = basename4(reactEntry);
1776
1821
  try {
1777
1822
  console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
1778
- const result = await deployReact(reactEntry, privateKey, config.api);
1823
+ const result = await deployReact(reactEntry, privateKey, config.api, options.curl);
1779
1824
  if (result.skipped) {
1780
1825
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1781
1826
  skipped++;
@@ -1797,7 +1842,7 @@ var deployAll = async (config, options) => {
1797
1842
  }
1798
1843
  };
1799
1844
  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) => {
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) => {
1801
1846
  let config;
1802
1847
  if (options.env) {
1803
1848
  config = getConfig(options.env);
@@ -1831,7 +1876,7 @@ var registerDeployCommand = (program) => {
1831
1876
  try {
1832
1877
  if (isReact(filePath)) {
1833
1878
  console.log(chalk3.yellow("Bundling React SPA ") + chalk3.bold(fileName));
1834
- const result = await deployReact(filePath, privateKey, config.api);
1879
+ const result = await deployReact(filePath, privateKey, config.api, options.curl);
1835
1880
  if (result.skipped) {
1836
1881
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1837
1882
  } else {
@@ -1839,7 +1884,7 @@ var registerDeployCommand = (program) => {
1839
1884
  }
1840
1885
  } else {
1841
1886
  console.log(chalk3.yellow("Bundling ") + chalk3.bold(fileName));
1842
- const result = await deployFunction(filePath, privateKey, config.api);
1887
+ const result = await deployFunction(filePath, privateKey, config.api, options.curl);
1843
1888
  if (result.skipped) {
1844
1889
  console.log(chalk3.gray("\u2013 Skipped ") + chalk3.bold(fileName) + chalk3.gray(" (unchanged)"));
1845
1890
  } else {
@@ -2256,10 +2301,10 @@ Secret keys:`));
2256
2301
  }));
2257
2302
  };
2258
2303
  // 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";
2304
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "fs";
2305
+ import { join as join6 } from "path";
2262
2306
  import { homedir } from "os";
2307
+ var {$: $2 } = globalThis.Bun;
2263
2308
  import chalk7 from "chalk";
2264
2309
  var PLATFORMS2 = {
2265
2310
  "linux-x64": "@tothalex/nulljs-linux-x64",
@@ -2289,8 +2334,8 @@ var generateProductionKeys = async () => {
2289
2334
  const publicKey = btoa(String.fromCharCode(...new Uint8Array(publicKeyBuffer)));
2290
2335
  return { privateKey, publicKey };
2291
2336
  };
2292
- var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
2293
- const configPath = join5(cloudPath, "config.json");
2337
+ var saveProductionConfig = async (cloudPath, privateKey, publicKey) => {
2338
+ const configPath = join6(cloudPath, "config.json");
2294
2339
  const config = {
2295
2340
  key: {
2296
2341
  private: privateKey,
@@ -2298,8 +2343,8 @@ var saveProductionConfig = (cloudPath, privateKey, publicKey) => {
2298
2343
  }
2299
2344
  };
2300
2345
  console.log(chalk7.blue("Saving production configuration..."));
2301
- writeFileSync2(configPath, JSON.stringify(config, null, 2));
2302
- execSync(`chmod 600 ${configPath}`, { stdio: "inherit" });
2346
+ writeFileSync3(configPath, JSON.stringify(config, null, 2));
2347
+ await $2`chmod 600 ${configPath}`;
2303
2348
  console.log(chalk7.green("Production configuration saved"));
2304
2349
  };
2305
2350
  var createSystemdService = (cloudPath, publicKey, serverBinPath, userName) => {
@@ -2337,14 +2382,15 @@ var ensureCloudDirectory = (cloudPath) => {
2337
2382
  console.log(chalk7.gray(`Cloud directory already exists: ${cloudPath}`));
2338
2383
  }
2339
2384
  };
2340
- var installSystemdService = (serviceContent) => {
2385
+ var installSystemdService = async (serviceContent) => {
2341
2386
  const servicePath = "/etc/systemd/system/nulljs.service";
2342
2387
  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"));
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"));
2348
2394
  };
2349
2395
  var validateLinux = () => {
2350
2396
  if (process.platform !== "linux") {
@@ -2360,7 +2406,7 @@ var validateServerBinary = (serverBinPath) => {
2360
2406
  }
2361
2407
  };
2362
2408
  var checkExistingProductionConfig = (cloudPath) => {
2363
- const configPath = join5(cloudPath, "config.json");
2409
+ const configPath = join6(cloudPath, "config.json");
2364
2410
  if (!existsSync7(configPath)) {
2365
2411
  return null;
2366
2412
  }
@@ -2380,13 +2426,13 @@ var unhost = async (options = {}) => {
2380
2426
  try {
2381
2427
  console.log(chalk7.blue("Stopping NullJS service..."));
2382
2428
  try {
2383
- execSync("sudo systemctl stop nulljs", { stdio: "inherit" });
2429
+ await $2`sudo systemctl stop nulljs`;
2384
2430
  console.log(chalk7.green("Service stopped"));
2385
2431
  } catch {
2386
2432
  console.log(chalk7.yellow("Service was not running"));
2387
2433
  }
2388
2434
  try {
2389
- execSync("sudo systemctl disable nulljs", { stdio: "inherit" });
2435
+ await $2`sudo systemctl disable nulljs`;
2390
2436
  console.log(chalk7.green("Service disabled"));
2391
2437
  } catch {
2392
2438
  console.log(chalk7.yellow("Service was not enabled"));
@@ -2394,15 +2440,15 @@ var unhost = async (options = {}) => {
2394
2440
  const servicePath = "/etc/systemd/system/nulljs.service";
2395
2441
  if (existsSync7(servicePath)) {
2396
2442
  console.log(chalk7.blue("Removing systemd service file..."));
2397
- execSync(`sudo rm ${servicePath}`, { stdio: "inherit" });
2398
- execSync("sudo systemctl daemon-reload", { stdio: "inherit" });
2443
+ await $2`sudo rm ${servicePath}`;
2444
+ await $2`sudo systemctl daemon-reload`;
2399
2445
  console.log(chalk7.green("Systemd service file removed"));
2400
2446
  }
2401
2447
  if (!options.keepData) {
2402
- const defaultCloudPath = join5(homedir(), ".nulljs");
2448
+ const defaultCloudPath = join6(homedir(), ".nulljs");
2403
2449
  if (existsSync7(defaultCloudPath)) {
2404
2450
  console.log(chalk7.blue("Removing cloud directory..."));
2405
- execSync(`rm -rf ${defaultCloudPath}`, { stdio: "inherit" });
2451
+ await $2`rm -rf ${defaultCloudPath}`;
2406
2452
  console.log(chalk7.green("Cloud directory removed"));
2407
2453
  } else {
2408
2454
  console.log(chalk7.gray("Cloud directory does not exist"));
@@ -2422,9 +2468,42 @@ var unhost = async (options = {}) => {
2422
2468
  process.exit(1);
2423
2469
  }
2424
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
+ };
2425
2504
  var host = async (cloudPath) => {
2426
2505
  validateLinux();
2427
- 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");
2428
2507
  console.log(chalk7.blue("Setting up NullJS production hosting..."));
2429
2508
  console.log(chalk7.gray(` Cloud path: ${resolvedCloudPath}`));
2430
2509
  const serverBinPath = getServerBinPath();
@@ -2435,7 +2514,7 @@ var host = async (cloudPath) => {
2435
2514
  let productionConfig = checkExistingProductionConfig(resolvedCloudPath);
2436
2515
  if (!productionConfig) {
2437
2516
  const { privateKey, publicKey } = await generateProductionKeys();
2438
- saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
2517
+ await saveProductionConfig(resolvedCloudPath, privateKey, publicKey);
2439
2518
  productionConfig = { key: { private: privateKey, public: publicKey } };
2440
2519
  console.log(chalk7.green("Production keys generated"));
2441
2520
  console.log(chalk7.blue("Production Public Key:"));
@@ -2446,32 +2525,33 @@ var host = async (cloudPath) => {
2446
2525
  console.log(chalk7.gray(productionConfig.key.public));
2447
2526
  }
2448
2527
  const serviceContent = createSystemdService(resolvedCloudPath, productionConfig.key.public, serverBinPath, currentUser);
2449
- installSystemdService(serviceContent);
2528
+ await installSystemdService(serviceContent);
2450
2529
  console.log(chalk7.green("NullJS hosting setup complete!"));
2451
2530
  console.log("");
2452
2531
  console.log(chalk7.blue("Service management commands:"));
2453
- console.log(chalk7.gray(" Start: "), chalk7.white("sudo systemctl start nulljs"));
2454
2532
  console.log(chalk7.gray(" Stop: "), chalk7.white("sudo systemctl stop nulljs"));
2533
+ console.log(chalk7.gray(" Restart: "), chalk7.white("sudo systemctl restart nulljs"));
2455
2534
  console.log(chalk7.gray(" Status: "), chalk7.white("sudo systemctl status nulljs"));
2456
2535
  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
2536
  };
2460
2537
  var registerHostCommand = (program) => {
2461
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) => {
2462
2539
  await host(cloudPath);
2463
2540
  });
2541
+ hostCmd.command("update").description("Update NullJS service after package upgrade").action(async () => {
2542
+ await update();
2543
+ });
2464
2544
  hostCmd.command("remove").description("Remove NullJS production hosting").option("--keep-data", "Keep cloud data directory").action(async (options) => {
2465
2545
  await unhost(options);
2466
2546
  });
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) => {
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) => {
2468
2548
  validateLinux();
2469
- const args = ["-u", "nulljs", "-n", options.lines];
2470
- if (options.follow) {
2471
- args.push("-f");
2472
- }
2473
2549
  try {
2474
- 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
+ }
2475
2555
  } catch {}
2476
2556
  });
2477
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.58",
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"