@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 +32 -3
- package/dist/log.d.ts +3 -0
- package/dist/log.js +5 -0
- package/dist/migrations.js +4 -2
- package/dist/plugin-discovery.js +6 -4
- package/dist/plugin-runtime.js +9 -7
- package/dist/plugin-updates.d.ts +8 -1
- package/dist/plugin-updates.js +10 -2
- package/dist/seeds.js +3 -1
- package/package.json +3 -2
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({
|
|
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
|
|
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
package/dist/log.js
ADDED
package/dist/migrations.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/plugin-discovery.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
82
|
+
log.warn(`${name}: no valid default manifest, skipping`);
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
83
85
|
catch (e) {
|
|
84
|
-
|
|
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
|
-
|
|
122
|
+
log.error(`${name}: server hooks not loaded — ${e.message}`);
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
return hooks;
|
package/dist/plugin-runtime.js
CHANGED
|
@@ -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
|
-
|
|
82
|
+
log.debug(`${p.id}: init ✓`);
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
catch (err) {
|
|
84
|
-
|
|
86
|
+
log.error(`${p.id}: initialization failure`, err);
|
|
85
87
|
throw err;
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
|
-
|
|
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
|
-
|
|
104
|
+
log.debug(`${p.id}: teardown ✓`);
|
|
103
105
|
}
|
|
104
106
|
catch (e) {
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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));
|
package/dist/plugin-updates.d.ts
CHANGED
|
@@ -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
|
|
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
|
}>;
|
package/dist/plugin-updates.js
CHANGED
|
@@ -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
|
|
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',
|
|
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
|
-
|
|
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
|
+
"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.
|
|
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": {
|