windsor-bot 0.2.6 → 0.2.8

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/dist/bot.js CHANGED
@@ -211,12 +211,15 @@ export function createWindsorBot() {
211
211
  const config = getReusableConfig(message);
212
212
  if (!config)
213
213
  return;
214
- if ([...message.reactions.cache.values()].some(candidate => candidate.me))
215
- return;
216
- if (reusableStates.has(message.id))
214
+ const emoji = reaction.emoji.name ?? reaction.emoji.id ?? 'unknown';
215
+ logEvent('info', `Reusable reaction added: message=${message.id}, channel=${message.channelId}, emoji=${emoji}, user=${user.id}`);
216
+ if (reusableStates.has(message.id)) {
217
+ logEvent('info', `Ignoring reusable reaction for message=${message.id}; print already in progress`);
217
218
  return;
219
+ }
218
220
  const state = { reaction, cancelled: false, status: 'printing' };
219
221
  reusableStates.set(message.id, state);
222
+ logEvent('info', `Starting reusable print for message=${message.id}`);
220
223
  await handleReusablePrint(message, config, state);
221
224
  }
222
225
  catch (err) {
@@ -237,6 +240,10 @@ export function createWindsorBot() {
237
240
  await removeReactionSafe(message, Reaction.waiting);
238
241
  if (state.status !== 'printing')
239
242
  reusableStates.delete(message.id);
243
+ logEvent('info', `Reusable reaction removed: message=${message.id}; cancelled ${state.status} print`);
244
+ }
245
+ else {
246
+ logEvent('info', `Reusable reaction removed: message=${message.id}; no active print`);
240
247
  }
241
248
  await removeOwnReactionSafe(reaction);
242
249
  }
@@ -631,13 +638,13 @@ export function createWindsorBot() {
631
638
  state.status = 'pending';
632
639
  await reactSafe(message, Reaction.waiting);
633
640
  enqueuePendingPrint(message, () => handleReusablePrint(message, config, state));
634
- logEvent('info', `Printer unavailable for reusable message in #${message.channelId}`);
641
+ logEvent('info', `Printer unavailable for reusable message=${message.id} in #${message.channelId}; waiting for retry`);
635
642
  return;
636
643
  }
637
644
  reusableStates.delete(message.id);
638
645
  await removeReactionSafe(message, Reaction.waiting);
639
646
  await replySafe(message, `⏸️ Print failed: ${err instanceof Error ? err.message : String(err)}`);
640
- logEvent('error', `Reusable List print failed: ${err}`);
647
+ logEvent('error', `Reusable List print failed: message=${message.id}: ${err}`);
641
648
  return;
642
649
  }
643
650
  pendingPrints.delete(message.id);
@@ -648,7 +655,7 @@ export function createWindsorBot() {
648
655
  }
649
656
  await message.react(state.reaction.emoji);
650
657
  state.status = 'printed';
651
- logEvent('print', `Printed reusable message from ${message.author.username}`);
658
+ logEvent('print', `Printed reusable message=${message.id} from ${message.author.username}; acknowledgement reaction added`);
652
659
  }
653
660
  async function retryFailedMessages(message) {
654
661
  const mapping = getCurrentConfig().channels.find(m => m.channelId === message.channelId);
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createServer } from 'http';
2
2
  import { execFile } from 'child_process';
3
3
  import { build } from 'esbuild';
4
- import { access, readFile, stat } from 'fs/promises';
4
+ import { access, constants, readFile, stat } from 'fs/promises';
5
5
  import { promisify } from 'util';
6
6
  import { spawn } from 'child_process';
7
7
  import { dirname, join } from 'path';
@@ -65,7 +65,7 @@ async function readPackageVersion() {
65
65
  return packageJson.version;
66
66
  }
67
67
  async function updateGlobalBot() {
68
- const npmCandidates = [
68
+ const configuredCandidates = [
69
69
  process.env['npm_execpath'],
70
70
  join(dirname(process.execPath), '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
71
71
  join(dirname(process.execPath), 'npm'),
@@ -76,11 +76,17 @@ async function updateGlobalBot() {
76
76
  '/usr/local/bin/npm',
77
77
  '/opt/homebrew/bin/npm',
78
78
  '/usr/bin/npm',
79
- 'npm',
80
79
  ]
81
80
  .filter((candidate) => Boolean(candidate))
82
81
  .map(candidate => candidate.trim())
83
82
  .filter((candidate, index, candidates) => candidates.indexOf(candidate) === index);
83
+ const pathCandidates = (process.env['PATH'] ?? '')
84
+ .split(':')
85
+ .filter(Boolean)
86
+ .map(directory => join(directory, 'npm'));
87
+ const npmCandidates = [...configuredCandidates, ...pathCandidates]
88
+ .filter((candidate, index, candidates) => candidates.indexOf(candidate) === index);
89
+ logEvent('info', `Update requested; checking ${npmCandidates.length} npm candidate(s) (node=${process.execPath}, cwd=${process.cwd()})`);
84
90
  let npmCommand;
85
91
  for (const candidate of npmCandidates) {
86
92
  const command = candidate.endsWith('.js')
@@ -88,19 +94,40 @@ async function updateGlobalBot() {
88
94
  : { path: candidate, args: [] };
89
95
  try {
90
96
  // Validate the script itself, not just the Node executable used to run it.
91
- if (candidate !== 'npm')
92
- await access(candidate);
97
+ await access(candidate, constants.R_OK | (candidate.endsWith('.js') ? 0 : constants.X_OK));
93
98
  npmCommand = command;
99
+ logEvent('info', `Using npm candidate ${candidate}${command.args.length > 0 ? ` via ${command.path}` : ''}`);
94
100
  break;
95
101
  }
96
- catch {
97
- // Try the next known npm installation location.
102
+ catch (error) {
103
+ const code = error instanceof Error && 'code' in error ? String(error.code) : 'unknown';
104
+ logEvent('info', `npm candidate unavailable: ${candidate} (${code})`);
98
105
  }
99
106
  }
100
- if (!npmCommand)
101
- throw new Error('Unable to locate npm');
107
+ if (!npmCommand) {
108
+ const path = process.env['PATH'] ?? '(unset)';
109
+ logEvent('error', `Unable to locate an executable npm; PATH=${path}`);
110
+ throw new Error('Unable to locate an executable npm; see diagnostics');
111
+ }
102
112
  const npm = npmCommand;
103
- const runNpm = (args) => execFileAsync(npm.path, [...npm.args, ...args], { env: process.env });
113
+ const runNpm = async (args) => {
114
+ const commandArgs = [...npm.args, ...args];
115
+ logEvent('info', `Running npm: ${npm.path} ${commandArgs.join(' ')}`);
116
+ try {
117
+ const result = await execFileAsync(npm.path, commandArgs, { env: process.env });
118
+ logEvent('info', `npm completed successfully: ${args.join(' ')}`);
119
+ return result;
120
+ }
121
+ catch (error) {
122
+ const details = error instanceof Error ? error : new Error(String(error));
123
+ const code = 'code' in details ? String(details.code) : 'unknown';
124
+ const stderr = 'stderr' in details && typeof details.stderr === 'string'
125
+ ? details.stderr.trim().slice(-1000)
126
+ : '';
127
+ logEvent('error', `npm failed (code=${code}, path=${npm.path}, args=${commandArgs.join(' ')})${stderr ? ` stderr=${stderr}` : ''}`);
128
+ throw details;
129
+ }
130
+ };
104
131
  await runNpm(['install', '-g', 'windsor-bot@latest']);
105
132
  const { stdout } = await runNpm(['root', '-g']);
106
133
  const packageJsonPath = join(stdout.trim(), 'windsor-bot', 'package.json');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windsor-bot",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Self-hosted Discord bot automation for household list and todo printing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",