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/README.md CHANGED
@@ -118,11 +118,18 @@ npm run dev
118
118
 
119
119
  Then open `http://localhost:8080` to use the web control panel.
120
120
 
121
+ When installed globally, run `windsor-bot` from the directory containing
122
+ `windsor.config.json`, or set `WINDSOR_CONFIG_PATH` to its full path:
123
+
124
+ ```bash
125
+ WINDSOR_CONFIG_PATH=/path/to/windsor.config.json windsor-bot
126
+ ```
127
+
121
128
  The control panel is a Preact app served by the diagnostics server and can:
122
129
 
123
130
  1. Show runtime bot status and recent events.
124
131
  2. Save basic setup into a local `windsor.config.json` file (Discord token, server id, etc).
125
132
 
126
- The browser app source lives in `src/web/app.tsx` and is bundled on-demand by the server using the esbuild API.
133
+ The browser app source lives in `src/web/app.tsx`. It is bundled into the npm package during the build and served by the diagnostics server; development runs can still bundle the source on demand.
127
134
 
128
135
  `windsor.config.json` is gitignored because it can contain secrets.
package/dist/index.js CHANGED
@@ -1,16 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { loadConfig, getCurrentConfig } from './config.js';
2
+ import { loadConfig } from './config.js';
3
3
  import { logEvent, startDiagnosticsServer, setDiscordChannels, setRestartHandler, setRefreshChannelsHandler } from './server.js';
4
4
  import { createWindsorBot, checkScheduledTasks } from './bot.js';
5
5
  import { ChannelType } from 'discord.js';
6
- const config = getCurrentConfig();
6
+ const { config } = await loadConfig();
7
7
  startDiagnosticsServer(config.diagnosticsPort);
8
8
  let bot = null;
9
9
  let startInFlight = false;
10
- async function startBot() {
10
+ async function startBot(cfg) {
11
11
  if (startInFlight)
12
12
  return;
13
- const cfg = getCurrentConfig();
14
13
  if (!cfg.discordToken) {
15
14
  logEvent('info', 'No Discord token configured. Visit the control panel to set up.');
16
15
  return;
@@ -51,8 +50,8 @@ export async function restartBot() {
51
50
  bot = null;
52
51
  }
53
52
  setDiscordChannels([]);
54
- await loadConfig();
55
- await startBot();
53
+ const { config } = await loadConfig();
54
+ await startBot(config);
56
55
  }
57
56
  // Recurring task checker
58
57
  setInterval(() => {
@@ -82,4 +81,4 @@ async function refreshChannels() {
82
81
  logEvent('info', `Refreshed ${channels.length} Discord channels`);
83
82
  }
84
83
  setRefreshChannelsHandler(refreshChannels);
85
- await startBot();
84
+ await startBot(config);
package/dist/server.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createServer } 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 { getCurrentConfig, updateConfig, checkPassword, hashPassword, getConfigFilePath, } from './config.js';
6
7
  import { printTestPage, getImageFeed, clearImageFeed, renderFeedHtml } from './printing/index.js';
7
8
  const MAX_EVENTS = 200;
@@ -73,13 +74,29 @@ async function readJsonBody(req) {
73
74
  }
74
75
  let appBundleCache = null;
75
76
  async function loadAppScript() {
76
- const entryPoint = join(process.cwd(), 'src', 'web', 'app.tsx');
77
- const sourceStat = await stat(entryPoint);
78
- if (appBundleCache && appBundleCache.mtimeMs === sourceStat.mtimeMs) {
77
+ const runtimeDir = dirname(fileURLToPath(import.meta.url));
78
+ const bundledApp = join(runtimeDir, 'web', 'app.bundle.js');
79
+ const sourceEntryPoint = join(runtimeDir, '..', 'src', 'web', 'app.tsx');
80
+ const appPath = await stat(bundledApp).then(() => bundledApp).catch((error) => {
81
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
82
+ return sourceEntryPoint;
83
+ }
84
+ throw error;
85
+ });
86
+ const appStat = await stat(appPath);
87
+ if (appPath === bundledApp) {
88
+ if (appBundleCache && appBundleCache.mtimeMs === appStat.mtimeMs) {
89
+ return appBundleCache.script;
90
+ }
91
+ const script = await readFile(appPath, 'utf8');
92
+ appBundleCache = { script, mtimeMs: appStat.mtimeMs };
93
+ return script;
94
+ }
95
+ if (appBundleCache && appBundleCache.mtimeMs === appStat.mtimeMs) {
79
96
  return appBundleCache.script;
80
97
  }
81
98
  const result = await build({
82
- entryPoints: [entryPoint],
99
+ entryPoints: [sourceEntryPoint],
83
100
  bundle: true,
84
101
  write: false,
85
102
  platform: 'browser',
@@ -92,7 +109,7 @@ async function loadAppScript() {
92
109
  const firstFile = result.outputFiles[0];
93
110
  if (!firstFile)
94
111
  throw new Error('Failed to generate app bundle');
95
- appBundleCache = { script: firstFile.text, mtimeMs: sourceStat.mtimeMs };
112
+ appBundleCache = { script: firstFile.text, mtimeMs: appStat.mtimeMs };
96
113
  return firstFile.text;
97
114
  }
98
115
  function renderHtmlShell() {