selfnify-host 1.0.1 → 1.0.3

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/host.js +41 -12
  2. package/package.json +1 -1
package/host.js CHANGED
@@ -9,7 +9,7 @@ const https = require('https');
9
9
  const crypto = require('crypto');
10
10
  const readline = require('readline');
11
11
 
12
- const VERSION = '1.0.1';
12
+ const VERSION = '1.0.2';
13
13
  const DEFAULT_URL = 'https://host.lume.dpdns.org';
14
14
  const CONFIG_DIR = path.join(os.homedir(), '.selfnify');
15
15
  const CONFIG_FILE = path.join(CONFIG_DIR, 'host.json');
@@ -254,7 +254,7 @@ function request(method, apiPath, opts) {
254
254
  });
255
255
  }
256
256
 
257
- const bp = (name, sub) => '/bots/' + encodeURIComponent(name) + (sub || '');
257
+ const bp = (name, sub) => '/bots/' + encodeURIComponent(String(name).toLowerCase()) + (sub || '');
258
258
 
259
259
  // ----------------------------------------------------------------- helpers
260
260
 
@@ -283,7 +283,7 @@ function parseArgs(argv) {
283
283
 
284
284
  function requireName(name, usage) {
285
285
  if (!name) throw new CliError('which bot? usage: ' + usage);
286
- return name;
286
+ return String(name).toLowerCase();
287
287
  }
288
288
 
289
289
  function parseEnvFile(file) {
@@ -315,14 +315,19 @@ function parseStart(str) {
315
315
  let lang = null;
316
316
  if (['python', 'python3', 'py'].includes(cmd)) lang = 'python';
317
317
  else if (['node', 'nodejs'].includes(cmd)) lang = 'node';
318
+ else if (['ts', 'typescript', 'tsx', 'ts-node'].includes(cmd)) lang = 'typescript';
318
319
  else if (['cargo', 'rust'].includes(cmd)) lang = 'rust';
319
- if (!lang) throw new CliError('don\'t know how to run "' + str + '". Use: python, node or cargo');
320
+ if (!lang && ['npm', 'npx', 'pnpm', 'yarn', 'bun'].includes(cmd)) {
321
+ throw new CliError('"' + cmd + '" is not a start command here. Use: start node (JavaScript), start ts (TypeScript), start python or start cargo');
322
+ }
323
+ if (!lang) throw new CliError('don\'t know how to run "' + str + '". Use: python, node, ts or cargo');
320
324
  return { lang, entry: tokens[1] || null };
321
325
  }
322
326
 
323
327
  function detectStart(dir) {
324
328
  const has = (f) => fs.existsSync(path.join(dir, f));
325
329
  if (has('Cargo.toml')) return 'cargo';
330
+ if (has('tsconfig.json')) return 'ts';
326
331
  if (has('package.json')) return 'node';
327
332
  if (has('requirements.txt') || has('main.py') || has('bot.py')) return 'python';
328
333
  return null;
@@ -338,7 +343,26 @@ function nodeMain(dir) {
338
343
  return 'index.js';
339
344
  }
340
345
 
341
- const defaultEntry = (lang) => ({ python: 'main.py', node: 'index.js', rust: '' }[lang]);
346
+ function readPkg(dir) {
347
+ try {
348
+ return JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
349
+ } catch (e) {
350
+ return {};
351
+ }
352
+ }
353
+
354
+ function hasBuildScript(dir) {
355
+ const pkg = readPkg(dir);
356
+ return Boolean(pkg.scripts && pkg.scripts.build);
357
+ }
358
+
359
+ function tsMain(dir) {
360
+ const pkg = readPkg(dir);
361
+ if (pkg.main) return pkg.main;
362
+ return hasBuildScript(dir) ? 'dist/index.js' : 'src/index.ts';
363
+ }
364
+
365
+ const defaultEntry = (lang) => ({ python: 'main.py', node: 'index.js', typescript: 'dist/index.js', rust: '' }[lang]);
342
366
  const sanitizeName = (s) => s.toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/^[-_]+/, '').slice(0, 32);
343
367
 
344
368
  async function packDir(dir) {
@@ -387,7 +411,7 @@ async function installBot(name, lang, progress) {
387
411
  }
388
412
  return secs + 's';
389
413
  }
390
- progress((lang === 'rust' ? 'building, Rust is slow, relax ' : 'installing ') + secs + 's');
414
+ progress((lang === 'rust' ? 'building, Rust is slow, relax ' : lang === 'typescript' ? 'installing and building ' : 'installing ') + secs + 's');
391
415
  if (secs > 3600) throw new CliError('install is taking over an hour. Check: host logs ' + name + ' --install');
392
416
  }
393
417
  }
@@ -617,10 +641,13 @@ async function deploy(opts, flags) {
617
641
  if (!startStr) throw new CliError('could not tell the language. Add: start python | node | cargo');
618
642
  const parsed = parseStart(startStr);
619
643
  const lang = parsed.lang;
620
- const main = opts.main || parsed.entry || (lang === 'node' ? nodeMain(dir) : defaultEntry(lang));
621
- const name = opts.name || sanitizeName(path.basename(dir));
644
+ const main = opts.main || parsed.entry || (lang === 'node' ? nodeMain(dir) : lang === 'typescript' ? tsMain(dir) : defaultEntry(lang));
645
+ const name = String(opts.name || sanitizeName(path.basename(dir))).toLowerCase();
622
646
  if (!NAME_RE.test(name)) throw new CliError('bad bot name "' + name + '": use a-z, 0-9, _ or -, max 32 characters');
623
- if (lang !== 'rust' && !fs.existsSync(path.join(dir, main))) throw new CliError('main file "' + main + '" not found in ' + dir);
647
+ const builtOnServer = lang === 'typescript' && !/\.(ts|mts|cts)$/.test(main) && hasBuildScript(dir);
648
+ if (lang !== 'rust' && !builtOnServer && !fs.existsSync(path.join(dir, main))) {
649
+ throw new CliError('main file "' + main + '" not found in ' + dir);
650
+ }
624
651
  let mem;
625
652
  if (flags.mem !== undefined) {
626
653
  mem = parseInt(flags.mem, 10);
@@ -695,7 +722,7 @@ async function deploy(opts, flags) {
695
722
 
696
723
  await next('Uploading', async ({ progress }) => {
697
724
  if (bot.status === 'installing') throw new CliError('an install is already running for this bot, try again in a minute');
698
- if (bot.status === 'running') {
725
+ if (bot.status === 'running' && lang !== 'rust') {
699
726
  progress('stopping old version');
700
727
  await request('POST', bp(name, '/stop'));
701
728
  }
@@ -735,7 +762,8 @@ async function deploy(opts, flags) {
735
762
 
736
763
  let finalLine = '';
737
764
  await next('Starting', async () => {
738
- await request('POST', bp(name, '/start'));
765
+ const swap = bot.status === 'running' && lang === 'rust';
766
+ await request('POST', bp(name, swap ? '/restart' : '/start'));
739
767
  await sleep(4000);
740
768
  const now = await request('GET', bp(name));
741
769
  if (now.status !== 'running') {
@@ -775,7 +803,8 @@ function printHelp() {
775
803
  out(' host <path> start <cmd> main <file> name <bot>');
776
804
  out(c.dim(' host ./mybot start node main index.js name mybot'));
777
805
  out(c.dim(' host . start python main main.py name musicbot'));
778
- out(c.dim(' start = python | node | cargo main = file to run name = bot name'));
806
+ out(c.dim(' start = python | node | ts | cargo main = file to run name = bot name'));
807
+ out(c.dim(' TypeScript: host ./mybot start ts name mybot (the server runs your "build" script)'));
779
808
  out(c.dim(' leave out start/main/name and it guesses from your folder'));
780
809
  out();
781
810
  h(' Manage');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selfnify-host",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Deploy and manage bots (Python, Node.js, Rust) on the Selfnify host with one command.",
5
5
  "bin": {
6
6
  "host": "host.js",