windsor-bot 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "windsor-bot",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Self-hosted Discord bot automation for household list and todo printing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "scripts": {
8
- "prepare": "tsc",
9
- "build": "tsc",
8
+ "prepare": "npm run build",
9
+ "build": "tsc && node scripts/build-web.mjs",
10
10
  "dev": "node src/index.ts",
11
11
  "test": "node --test src/tests/*.test.ts"
12
12
  },
@@ -0,0 +1,13 @@
1
+ import { build } from 'esbuild';
2
+
3
+ await build({
4
+ entryPoints: ['src/web/app.tsx'],
5
+ bundle: true,
6
+ outfile: 'dist/web/app.bundle.js',
7
+ platform: 'browser',
8
+ format: 'esm',
9
+ target: ['es2022'],
10
+ jsx: 'automatic',
11
+ jsxImportSource: 'preact',
12
+ sourcemap: 'inline',
13
+ });
package/src/index.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { loadConfig, getCurrentConfig } from './config.ts';
2
+ import { loadConfig } from './config.ts';
3
3
  import { logEvent, startDiagnosticsServer, setDiscordChannels, setRestartHandler, setRefreshChannelsHandler } from './server.ts';
4
4
  import { createWindsorBot, checkScheduledTasks } from './bot.ts';
5
5
  import { ChannelType } from 'discord.js';
6
+ import type { WindsorConfig } from './types.ts';
6
7
 
7
- const config = getCurrentConfig();
8
+ const { config } = await loadConfig();
8
9
 
9
10
  startDiagnosticsServer(config.diagnosticsPort);
10
11
 
11
12
  let bot: ReturnType<typeof createWindsorBot> | null = null;
12
13
  let startInFlight = false;
13
14
 
14
- async function startBot(): Promise<void> {
15
+ async function startBot(cfg: WindsorConfig): Promise<void> {
15
16
  if (startInFlight) return;
16
- const cfg = getCurrentConfig();
17
17
  if (!cfg.discordToken) {
18
18
  logEvent('info', 'No Discord token configured. Visit the control panel to set up.');
19
19
  return;
@@ -58,8 +58,8 @@ export async function restartBot(): Promise<void> {
58
58
  bot = null;
59
59
  }
60
60
  setDiscordChannels([]);
61
- await loadConfig();
62
- await startBot();
61
+ const { config } = await loadConfig();
62
+ await startBot(config);
63
63
  }
64
64
 
65
65
  // Recurring task checker
@@ -95,4 +95,4 @@ async function refreshChannels(): Promise<void> {
95
95
  }
96
96
 
97
97
  setRefreshChannelsHandler(refreshChannels);
98
- await startBot();
98
+ await startBot(config);
package/src/server.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createServer, type IncomingMessage, type ServerResponse } from 'http';
2
2
  import { build } from 'esbuild';
3
- import { stat } from 'fs/promises';
4
- import { join } from 'path';
3
+ import { readFile, stat } from 'fs/promises';
4
+ import { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
5
6
  import {
6
7
  getCurrentConfig,
7
8
  updateConfig,
@@ -90,14 +91,30 @@ async function readJsonBody(req: IncomingMessage): Promise<Record<string, unknow
90
91
  let appBundleCache: { script: string; mtimeMs: number } | null = null;
91
92
 
92
93
  async function loadAppScript(): Promise<string> {
93
- const entryPoint = join(process.cwd(), 'src', 'web', 'app.tsx');
94
- const sourceStat = await stat(entryPoint);
95
- if (appBundleCache && appBundleCache.mtimeMs === sourceStat.mtimeMs) {
94
+ const runtimeDir = dirname(fileURLToPath(import.meta.url));
95
+ const bundledApp = join(runtimeDir, 'web', 'app.bundle.js');
96
+ const sourceEntryPoint = join(runtimeDir, '..', 'src', 'web', 'app.tsx');
97
+ const appPath = await stat(bundledApp).then(() => bundledApp).catch((error: unknown) => {
98
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
99
+ return sourceEntryPoint;
100
+ }
101
+ throw error;
102
+ });
103
+ const appStat = await stat(appPath);
104
+ if (appPath === bundledApp) {
105
+ if (appBundleCache && appBundleCache.mtimeMs === appStat.mtimeMs) {
106
+ return appBundleCache.script;
107
+ }
108
+ const script = await readFile(appPath, 'utf8');
109
+ appBundleCache = { script, mtimeMs: appStat.mtimeMs };
110
+ return script;
111
+ }
112
+ if (appBundleCache && appBundleCache.mtimeMs === appStat.mtimeMs) {
96
113
  return appBundleCache.script;
97
114
  }
98
115
 
99
116
  const result = await build({
100
- entryPoints: [entryPoint],
117
+ entryPoints: [sourceEntryPoint],
101
118
  bundle: true,
102
119
  write: false,
103
120
  platform: 'browser',
@@ -110,7 +127,7 @@ async function loadAppScript(): Promise<string> {
110
127
 
111
128
  const firstFile = result.outputFiles[0];
112
129
  if (!firstFile) throw new Error('Failed to generate app bundle');
113
- appBundleCache = { script: firstFile.text, mtimeMs: sourceStat.mtimeMs };
130
+ appBundleCache = { script: firstFile.text, mtimeMs: appStat.mtimeMs };
114
131
  return firstFile.text;
115
132
  }
116
133