@coffer-org/server 1.3.1 → 1.5.0

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/index.js CHANGED
@@ -28,10 +28,11 @@ import { initPlugins, teardownPlugins, readDisabled, purgePluginData, getPluginS
28
28
  import { registerPluginsApi } from "./plugins-api.js";
29
29
  import { registerAuthApi, resolveRequestUser, requireAdmin, PUBLIC_API_PATHS } from "./auth-api.js";
30
30
  import { discoverPluginAssets } from "./plugin-discovery.js";
31
- import { checkLatestVersion, resolveUpdateTarget, runNpmInstall } from "./plugin-updates.js";
31
+ import { checkLatestVersion, resolveUpdateTarget, resolveAllUpdateTargets, runNpmInstall } from "./plugin-updates.js";
32
32
  import { buildClientSchema } from "./schema-api.js";
33
33
  import { recordList, recordGet, recordCreate, recordUpdate, recordDelete, rowMatch, UnknownTypeError } from "./records-api.js";
34
34
  import { maskTree, preserveTree } from "./secrets.js";
35
+ import { rootLogger, getLogger } from "./log.js";
35
36
  import { uploadsDir } from "./uploads.js";
36
37
  const ENV_FILE = join(process.cwd(), '.env');
37
38
  if (existsSync(ENV_FILE))
@@ -46,7 +47,20 @@ for (const m of modules) {
46
47
  moduleDefs.set(`${m.vault}/${m.module}`, m);
47
48
  }
48
49
  const UPLOADS = uploadsDir();
49
- const app = Fastify({ logger: true });
50
+ const app = Fastify({
51
+ loggerInstance: rootLogger,
52
+ disableRequestLogging: true,
53
+ });
54
+ const httpLog = getLogger('http');
55
+ app.addHook('onResponse', async (req, reply) => {
56
+ const level = reply.statusCode >= 500 ? 'error' : reply.statusCode >= 400 ? 'warn' : 'debug';
57
+ httpLog[level]('request', {
58
+ method: req.method,
59
+ url: req.url,
60
+ status: reply.statusCode,
61
+ ms: Number(reply.elapsedTime.toFixed(1)),
62
+ });
63
+ });
50
64
  await app.register(cors, { origin: true });
51
65
  await app.register(multipart, { limits: { fileSize: 25 * 1024 * 1024 } });
52
66
  await app.register(fastifyStatic, { root: UPLOADS, prefix: '/uploads/' });
@@ -220,13 +234,28 @@ app.post('/api/plugins/:id/update', async (req, reply) => {
220
234
  if (!target.ok) {
221
235
  return reply.code(target.error === 'unknown_plugin' ? 404 : 400).send({ error: target.error });
222
236
  }
223
- const result = await runNpmInstall(target.packageName, target.version, process.cwd());
237
+ const result = await runNpmInstall([`${target.packageName}@${target.version}`], process.cwd());
224
238
  if (!result.ok) {
225
239
  return reply.code(500).send({ error: 'install_failed', stderr: result.stderr });
226
240
  }
227
241
  reply.send({ ok: true });
228
242
  setTimeout(() => process.exit(0), 500);
229
243
  });
244
+ app.post('/api/plugins/update-all', async (req, reply) => {
245
+ if (!requireAdmin(req, reply))
246
+ return;
247
+ const assets = await discoverPluginAssets();
248
+ const latestById = new Map(await Promise.all(assets.map(async (a) => [a.id, await checkLatestVersion(a.packageName)])));
249
+ const targets = resolveAllUpdateTargets(assets, latestById);
250
+ if (targets.length === 0)
251
+ return reply.code(400).send({ error: 'no_update_available' });
252
+ const result = await runNpmInstall(targets.map((t) => `${t.packageName}@${t.to}`), process.cwd());
253
+ if (!result.ok) {
254
+ return reply.code(500).send({ error: 'install_failed', stderr: result.stderr });
255
+ }
256
+ reply.send({ ok: true, updated: targets.map(({ id, from, to }) => ({ id, from, to })) });
257
+ setTimeout(() => process.exit(0), 500);
258
+ });
230
259
  app.get('/api/plugins/:id/settings', async (req, reply) => {
231
260
  if (!requireAdmin(req, reply))
232
261
  return;
package/dist/log.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { getLogger } from '@coffer-org/sdk/logger';
2
+ export declare const rootLogger: import("pino").Logger<never, boolean>;
3
+ export { getLogger };
package/dist/log.js ADDED
@@ -0,0 +1,5 @@
1
+ import { pino } from 'pino';
2
+ import { setRootLogger, LOG_LEVEL, getLogger } from '@coffer-org/sdk/logger';
3
+ export const rootLogger = pino({ level: LOG_LEVEL });
4
+ setRootLogger(rootLogger);
5
+ export { getLogger };
@@ -1,5 +1,7 @@
1
1
  import { DatabaseSchema } from '@mikro-orm/sql';
2
2
  import { dialectOf } from "./dialect.js";
3
+ import { getLogger } from '@coffer-org/sdk/logger';
4
+ const log = getLogger('migrations');
3
5
  function sqlType(col) {
4
6
  if (col === 'integer' || col === 'boolean')
5
7
  return 'integer';
@@ -161,7 +163,7 @@ export async function runMigrations({ em, plugins, hooks }) {
161
163
  if (p.tables?.length && !(await anyTableExists(em, p.tables))) {
162
164
  await conn.execute(`INSERT INTO _migrations (plugin_id, version) VALUES (?, ?)
163
165
  ON CONFLICT(plugin_id) DO UPDATE SET version = excluded.version`, [p.id, maxV]);
164
- console.log(`[migrations] ${p.id}: fresh DB → stamp v${maxV} (no run)`);
166
+ log.info(`${p.id}: fresh DB → stamp v${maxV} (no run)`);
165
167
  continue;
166
168
  }
167
169
  const pending = [...list].filter((m) => m.version > stored).sort((a, b) => a.version - b.version);
@@ -172,7 +174,7 @@ export async function runMigrations({ em, plugins, hooks }) {
172
174
  ON CONFLICT(plugin_id) DO UPDATE SET version = excluded.version`, [p.id, m.version]);
173
175
  }
174
176
  if (pending.length)
175
- console.log(`[migrations] ${p.id}: applied ${pending.length} (→ v${pending[pending.length - 1].version})`);
177
+ log.info(`${p.id}: applied ${pending.length} (→ v${pending[pending.length - 1].version})`);
176
178
  }
177
179
  }
178
180
  export async function assertSafeRequired(em, entities) {
@@ -9,6 +9,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
9
9
  import { readdir, readFile } from 'node:fs/promises';
10
10
  import { join } from 'node:path';
11
11
  import { pathToFileURL } from 'node:url';
12
+ import { getLogger } from '@coffer-org/sdk/logger';
13
+ const log = getLogger('discovery');
12
14
  async function pkgNames(nm) {
13
15
  const names = [];
14
16
  try {
@@ -63,7 +65,7 @@ export async function discoverPluginAssets() {
63
65
  });
64
66
  }
65
67
  catch (e) {
66
- console.error(`[discovery] ${name}: asset scan skip — ${e.message}`);
68
+ log.error(`${name}: asset scan skip — ${e.message}`);
67
69
  }
68
70
  }
69
71
  return out;
@@ -77,11 +79,11 @@ async function loadManifest(nm, name) {
77
79
  const manifest = mod.default;
78
80
  if (manifest && typeof manifest.id === 'string')
79
81
  return manifest;
80
- console.warn(`[discovery] ${name}: no valid default manifest, skipping`);
82
+ log.warn(`${name}: no valid default manifest, skipping`);
81
83
  return null;
82
84
  }
83
85
  catch (e) {
84
- console.error(`[discovery] ${name}: import failed, skipping — ${e.message}`);
86
+ log.error(`${name}: import failed, skipping — ${e.message}`);
85
87
  return null;
86
88
  }
87
89
  }
@@ -117,7 +119,7 @@ export async function loadServerHooks() {
117
119
  hooks[id] = mod.serverHooks;
118
120
  }
119
121
  catch (e) {
120
- console.error(`[discovery] ${name}: server hooks not loaded — ${e.message}`);
122
+ log.error(`${name}: server hooks not loaded — ${e.message}`);
121
123
  }
122
124
  }
123
125
  return hooks;
@@ -1,5 +1,6 @@
1
1
  import { serialize } from '@mikro-orm/core';
2
2
  import { composeRegistry } from '@coffer-org/core/compose';
3
+ import { getLogger } from '@coffer-org/sdk/logger';
3
4
  import { initDb, getOrm, getEm } from "./db.js";
4
5
  import { syncSchema } from "./schema-sync.js";
5
6
  import { systemEntities, buildPluginEntities, moduleTableName } from "./entity-schema.js";
@@ -9,6 +10,7 @@ import { runMigrations, assertSafeRequired } from "./migrations.js";
9
10
  import { runSeeds } from "./seeds.js";
10
11
  import { setActiveRegistry } from "./registry-context.js";
11
12
  import { migrateEmbeddingVectorsToBlob } from "./embeddings.js";
13
+ const log = getLogger('plugins');
12
14
  let _plugins = null;
13
15
  export async function getPlugins() {
14
16
  return (_plugins ??= await discoverPlugins());
@@ -77,15 +79,15 @@ export async function initPlugins() {
77
79
  try {
78
80
  if (h?.init) {
79
81
  await h.init({ em: getEm().fork() });
80
- console.log(`[plugin] ${p.id}: init ✓`);
82
+ log.debug(`${p.id}: init ✓`);
81
83
  }
82
84
  }
83
85
  catch (err) {
84
- console.error(`[plugin] ${p.id}: initialization failure`, err);
86
+ log.error(`${p.id}: initialization failure`, err);
85
87
  throw err;
86
88
  }
87
89
  }
88
- console.log(`[plugin] active: ${reg.order.map((p) => p.id).join(' ')}` +
90
+ log.info(`active: ${reg.order.map((p) => p.id).join(' ')}` +
89
91
  (disabled.size ? ` | disabled: ${[...disabled].join(' ')}` : ''));
90
92
  return reg;
91
93
  }
@@ -99,10 +101,10 @@ export async function teardownPlugins() {
99
101
  continue;
100
102
  try {
101
103
  await h.teardown({ em: getEm().fork() });
102
- console.log(`[plugin] ${p.id}: teardown ✓`);
104
+ log.debug(`${p.id}: teardown ✓`);
103
105
  }
104
106
  catch (e) {
105
- console.warn(`[plugin] ${p.id}: teardown ✗ ${e.message}`);
107
+ log.warn(`${p.id}: teardown ✗ ${e.message}`);
106
108
  }
107
109
  }
108
110
  }
@@ -110,7 +112,7 @@ export async function teardownPlugin(id) {
110
112
  const h = pluginHooks[id];
111
113
  if (h?.teardown) {
112
114
  await h.teardown({ em: getEm().fork() });
113
- console.log(`[plugin] ${id}: teardown ✓`);
115
+ log.debug(`${id}: teardown ✓`);
114
116
  }
115
117
  }
116
118
  function pluginModules(p) {
@@ -154,7 +156,7 @@ export async function purgePluginData(p, actor) {
154
156
  after: null,
155
157
  });
156
158
  });
157
- console.log(`[plugin] ${p.id}: purge ✓ (${tables.length} tables)`);
159
+ log.info(`${p.id}: purge ✓ (${tables.length} tables)`);
158
160
  }
159
161
  export function filterEnabled(all, disabled) {
160
162
  return all.filter((p) => !disabled.has(p.id));
@@ -9,7 +9,14 @@ export type UpdateTarget = {
9
9
  error: 'unknown_plugin' | 'no_update_available';
10
10
  };
11
11
  export declare function resolveUpdateTarget(assets: PluginAssetRecord[], id: string, latestVersion: string | null): UpdateTarget;
12
- export declare function runNpmInstall(packageName: string, version: string, cwd: string): Promise<{
12
+ export interface AllUpdateTarget {
13
+ id: string;
14
+ packageName: string;
15
+ from: string;
16
+ to: string;
17
+ }
18
+ export declare function resolveAllUpdateTargets(assets: PluginAssetRecord[], latestById: Map<string, string | null>): AllUpdateTarget[];
19
+ export declare function runNpmInstall(specs: string[], cwd: string): Promise<{
13
20
  ok: boolean;
14
21
  stderr: string;
15
22
  }>;
@@ -41,9 +41,17 @@ export function resolveUpdateTarget(assets, id, latestVersion) {
41
41
  return { ok: false, error: 'no_update_available' };
42
42
  return { ok: true, packageName: rec.packageName, version: latestVersion };
43
43
  }
44
- export function runNpmInstall(packageName, version, cwd) {
44
+ export function resolveAllUpdateTargets(assets, latestById) {
45
+ return assets.flatMap((rec) => {
46
+ const latest = latestById.get(rec.id) ?? null;
47
+ if (!latest || latest === rec.version)
48
+ return [];
49
+ return [{ id: rec.id, packageName: rec.packageName, from: rec.version, to: latest }];
50
+ });
51
+ }
52
+ export function runNpmInstall(specs, cwd) {
45
53
  return new Promise((resolve) => {
46
- execFile('npm', ['install', `${packageName}@${version}`], { cwd, timeout: 120_000, encoding: 'utf8' }, (err, _stdout, stderr) => {
54
+ execFile('npm', ['install', ...specs], { cwd, timeout: 120_000 + 30_000 * specs.length, encoding: 'utf8' }, (err, _stdout, stderr) => {
47
55
  if (err)
48
56
  return resolve({ ok: false, stderr: (stderr || err.message || '').trim() });
49
57
  resolve({ ok: true, stderr: '' });
package/dist/seeds.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { getLogger } from '@coffer-org/sdk/logger';
2
+ const log = getLogger('seeds');
1
3
  export function validateSeeds(pluginId, list) {
2
4
  const names = list.map((s) => s.name);
3
5
  if (new Set(names).size !== names.length) {
@@ -18,6 +20,6 @@ export async function runSeeds({ em, plugins, hooks }) {
18
20
  await conn.execute(`INSERT INTO _seeds (plugin_id, name) VALUES (?, ?)`, [p.id, s.name]);
19
21
  }
20
22
  if (pending.length)
21
- console.log(`[seeds] ${p.id}: applied ${pending.length} (${pending.map((s) => s.name).join(', ')})`);
23
+ log.info(`${p.id}: applied ${pending.length} (${pending.map((s) => s.name).join(', ')})`);
22
24
  }
23
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/server",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@coffer-org/core": "^1.3.0",
28
- "@coffer-org/sdk": "^1.3.1",
28
+ "@coffer-org/sdk": "^1.4.0",
29
29
  "@extractus/oembed-extractor": "^4.1.0",
30
30
  "@fastify/cors": "^11.2.0",
31
31
  "@fastify/multipart": "^10.0.0",
@@ -34,6 +34,7 @@
34
34
  "@mikro-orm/sqlite": "^7.1.4",
35
35
  "fastify": "^5.2.1",
36
36
  "open-graph-scraper": "^6.11.0",
37
+ "pino": "^9.14.0",
37
38
  "zod": "^4.4.3"
38
39
  },
39
40
  "optionalDependencies": {