@coffer-org/server 1.6.0 → 1.7.1

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
@@ -27,8 +27,8 @@ import { resolveWithinHome, filterSort, listDir } from "./fs-list.js";
27
27
  import { initPlugins, teardownPlugins, readDisabled, purgePluginData, getPluginSettings, getPlugins, } from "./plugin-runtime.js";
28
28
  import { registerPluginsApi } from "./plugins-api.js";
29
29
  import { registerAuthApi, resolveRequestUser, requireAdmin, PUBLIC_API_PATHS } from "./auth-api.js";
30
- import { discoverPluginAssets } from "./plugin-discovery.js";
31
- import { checkLatestVersion, resolveUpdateTarget, resolveAllUpdateTargets, runNpmInstall } from "./plugin-updates.js";
30
+ import { discoverPluginAssets, discoverRuntime } from "./plugin-discovery.js";
31
+ import { checkLatestVersion, resolveUpdateTarget, resolveAllUpdateTargets, resolveRuntimeTarget, 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";
@@ -229,7 +229,7 @@ app.post('/api/plugins/:id/update', async (req, reply) => {
229
229
  const { id } = req.params;
230
230
  const assets = await discoverPluginAssets();
231
231
  const rec = assets.find((a) => a.id === id);
232
- const latest = rec ? await checkLatestVersion(rec.packageName) : null;
232
+ const latest = rec && !rec.local ? await checkLatestVersion(rec.packageName) : null;
233
233
  const target = resolveUpdateTarget(assets, id, latest);
234
234
  if (!target.ok) {
235
235
  return reply.code(target.error === 'unknown_plugin' ? 404 : 400).send({ error: target.error });
@@ -245,15 +245,28 @@ app.post('/api/plugins/update-all', async (req, reply) => {
245
245
  if (!requireAdmin(req, reply))
246
246
  return;
247
247
  const assets = await discoverPluginAssets();
248
- const latestById = new Map(await Promise.all(assets.map(async (a) => [a.id, await checkLatestVersion(a.packageName)])));
248
+ const runtime = await discoverRuntime();
249
+ const [latestById, runtimeLatest] = await Promise.all([
250
+ Promise.all(assets.map(async (a) => [a.id, a.local ? null : await checkLatestVersion(a.packageName)])).then((entries) => new Map(entries)),
251
+ runtime && !runtime.local ? checkLatestVersion(runtime.packageName) : Promise.resolve(null),
252
+ ]);
249
253
  const targets = resolveAllUpdateTargets(assets, latestById);
250
- if (targets.length === 0)
254
+ const runtimeTarget = resolveRuntimeTarget(runtime, runtimeLatest);
255
+ const specs = [
256
+ ...targets.map((t) => `${t.packageName}@${t.to}`),
257
+ ...(runtimeTarget ? [`${runtimeTarget.packageName}@${runtimeTarget.to}`] : []),
258
+ ];
259
+ if (specs.length === 0)
251
260
  return reply.code(400).send({ error: 'no_update_available' });
252
- const result = await runNpmInstall(targets.map((t) => `${t.packageName}@${t.to}`), process.cwd());
261
+ const result = await runNpmInstall(specs, process.cwd());
253
262
  if (!result.ok) {
254
263
  return reply.code(500).send({ error: 'install_failed', stderr: result.stderr });
255
264
  }
256
- reply.send({ ok: true, updated: targets.map(({ id, from, to }) => ({ id, from, to })) });
265
+ const updated = [
266
+ ...targets.map(({ id, from, to }) => ({ id, from, to })),
267
+ ...(runtimeTarget ? [{ id: 'runtime', from: runtimeTarget.from, to: runtimeTarget.to }] : []),
268
+ ];
269
+ reply.send({ ok: true, updated });
257
270
  setTimeout(() => process.exit(0), 500);
258
271
  });
259
272
  app.get('/api/plugins/:id/settings', async (req, reply) => {
package/dist/log.js CHANGED
@@ -1,5 +1,22 @@
1
1
  import { pino } from 'pino';
2
2
  import { setRootLogger, LOG_LEVEL, getLogger } from '@coffer-org/sdk/logger';
3
- export const rootLogger = pino({ level: LOG_LEVEL });
3
+ const prettyEnv = process.env.COFFER_LOG_PRETTY;
4
+ const pretty = prettyEnv !== undefined ? prettyEnv !== '0' && prettyEnv !== '' : process.stdout.isTTY;
5
+ export const rootLogger = pino({
6
+ level: LOG_LEVEL,
7
+ ...(pretty
8
+ ? {
9
+ transport: {
10
+ target: 'pino-pretty',
11
+ options: {
12
+ colorize: true,
13
+ translateTime: 'SYS:HH:MM:ss',
14
+ ignore: 'pid,hostname,tag',
15
+ messageFormat: '{if tag}[{tag}] {end}{msg}',
16
+ },
17
+ },
18
+ }
19
+ : {}),
20
+ });
4
21
  setRootLogger(rootLogger);
5
22
  export { getLogger };
@@ -19,7 +19,14 @@ export interface PluginAssetRecord {
19
19
  schema: string;
20
20
  web?: string;
21
21
  css?: string;
22
+ local?: boolean;
22
23
  }
23
24
  export declare function discoverPluginAssets(): Promise<PluginAssetRecord[]>;
25
+ export interface RuntimeRecord {
26
+ packageName: string;
27
+ installedVersion: string;
28
+ local: boolean;
29
+ }
30
+ export declare function discoverRuntime(): Promise<RuntimeRecord | null>;
24
31
  export declare function discoverPlugins(): Promise<PluginManifest[]>;
25
32
  export declare function loadServerHooks(): Promise<Record<string, PluginHooks>>;
@@ -6,8 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- import { readdir, readFile } from 'node:fs/promises';
10
- import { join } from 'node:path';
9
+ import { readdir, readFile, realpath } from 'node:fs/promises';
10
+ import { join, sep } from 'node:path';
11
11
  import { pathToFileURL } from 'node:url';
12
12
  import { getLogger } from '@coffer-org/sdk/logger';
13
13
  const log = getLogger('discovery');
@@ -40,6 +40,15 @@ export function resolveAssetUrls(id, coffer) {
40
40
  out.css = `${base}/web.css`;
41
41
  return out;
42
42
  }
43
+ async function isLocalPackage(dir) {
44
+ try {
45
+ const real = await realpath(dir);
46
+ return !real.includes(`${sep}node_modules${sep}`);
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
43
52
  export async function discoverPluginAssets() {
44
53
  const nm = join(process.cwd(), 'node_modules');
45
54
  const names = await pkgNames(nm);
@@ -62,6 +71,7 @@ export async function discoverPluginAssets() {
62
71
  schema: urls.schema,
63
72
  web: urls.web,
64
73
  css: urls.css,
74
+ local: await isLocalPackage(join(nm, name)),
65
75
  });
66
76
  }
67
77
  catch (e) {
@@ -70,6 +80,19 @@ export async function discoverPluginAssets() {
70
80
  }
71
81
  return out;
72
82
  }
83
+ const RUNTIME_PACKAGE = '@coffer-org/meta';
84
+ export async function discoverRuntime() {
85
+ const dir = join(process.cwd(), 'node_modules', RUNTIME_PACKAGE);
86
+ try {
87
+ const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'));
88
+ if (!pkg.version)
89
+ return null;
90
+ return { packageName: RUNTIME_PACKAGE, installedVersion: pkg.version, local: await isLocalPackage(dir) };
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
73
96
  async function loadManifest(nm, name) {
74
97
  try {
75
98
  const pkg = JSON.parse(await readFile(join(nm, name, 'package.json'), 'utf8'));
@@ -1,6 +1,7 @@
1
1
  import type { EntityManager } from '@mikro-orm/core';
2
2
  import type { z } from 'zod';
3
3
  import type { ColumnType } from '@coffer-org/sdk/fields';
4
+ import { type Logger } from '@coffer-org/sdk/logger';
4
5
  export interface TableOps {
5
6
  renameColumn(from: string, to: string): Promise<void>;
6
7
  fill(column: string, value: string | number | boolean | null, opts?: {
@@ -20,7 +21,9 @@ export interface Migration {
20
21
  }
21
22
  export interface PluginCtx {
22
23
  em: EntityManager;
24
+ log: Logger;
23
25
  }
26
+ export declare function pluginCtx(id: string, em: EntityManager): PluginCtx;
24
27
  export interface Seed {
25
28
  name: string;
26
29
  run(ctx: PluginCtx): Promise<void> | void;
@@ -1 +1,5 @@
1
+ import { getLogger } from '@coffer-org/sdk/logger';
2
+ export function pluginCtx(id, em) {
3
+ return { em, log: getLogger(id) };
4
+ }
1
5
  export const pluginHooks = {};
@@ -4,7 +4,7 @@ import { getLogger } from '@coffer-org/sdk/logger';
4
4
  import { initDb, getOrm, getEm } from "./db.js";
5
5
  import { syncSchema } from "./schema-sync.js";
6
6
  import { systemEntities, buildPluginEntities, moduleTableName } from "./entity-schema.js";
7
- import { pluginHooks } from "./plugin-hooks.js";
7
+ import { pluginHooks, pluginCtx } from "./plugin-hooks.js";
8
8
  import { discoverPlugins, loadServerHooks } from "./plugin-discovery.js";
9
9
  import { runMigrations, assertSafeRequired } from "./migrations.js";
10
10
  import { runSeeds } from "./seeds.js";
@@ -78,7 +78,7 @@ export async function initPlugins() {
78
78
  const h = pluginHooks[p.id];
79
79
  try {
80
80
  if (h?.init) {
81
- await h.init({ em: getEm().fork() });
81
+ await h.init(pluginCtx(p.id, getEm().fork()));
82
82
  log.debug(`${p.id}: init ✓`);
83
83
  }
84
84
  }
@@ -100,7 +100,7 @@ export async function teardownPlugins() {
100
100
  if (!h?.teardown)
101
101
  continue;
102
102
  try {
103
- await h.teardown({ em: getEm().fork() });
103
+ await h.teardown(pluginCtx(p.id, getEm().fork()));
104
104
  log.debug(`${p.id}: teardown ✓`);
105
105
  }
106
106
  catch (e) {
@@ -111,7 +111,7 @@ export async function teardownPlugins() {
111
111
  export async function teardownPlugin(id) {
112
112
  const h = pluginHooks[id];
113
113
  if (h?.teardown) {
114
- await h.teardown({ em: getEm().fork() });
114
+ await h.teardown(pluginCtx(id, getEm().fork()));
115
115
  log.debug(`${id}: teardown ✓`);
116
116
  }
117
117
  }
@@ -16,6 +16,16 @@ export interface AllUpdateTarget {
16
16
  to: string;
17
17
  }
18
18
  export declare function resolveAllUpdateTargets(assets: PluginAssetRecord[], latestById: Map<string, string | null>): AllUpdateTarget[];
19
+ export interface RuntimeUpdateTarget {
20
+ packageName: string;
21
+ from: string;
22
+ to: string;
23
+ }
24
+ export declare function resolveRuntimeTarget(runtime: {
25
+ packageName: string;
26
+ installedVersion: string;
27
+ local: boolean;
28
+ } | null, latestVersion: string | null): RuntimeUpdateTarget | null;
19
29
  export declare function runNpmInstall(specs: string[], cwd: string): Promise<{
20
30
  ok: boolean;
21
31
  stderr: string;
@@ -49,6 +49,13 @@ export function resolveAllUpdateTargets(assets, latestById) {
49
49
  return [{ id: rec.id, packageName: rec.packageName, from: rec.version, to: latest }];
50
50
  });
51
51
  }
52
+ export function resolveRuntimeTarget(runtime, latestVersion) {
53
+ if (!runtime || runtime.local)
54
+ return null;
55
+ if (!latestVersion || latestVersion === runtime.installedVersion)
56
+ return null;
57
+ return { packageName: runtime.packageName, from: runtime.installedVersion, to: latestVersion };
58
+ }
52
59
  export function runNpmInstall(specs, cwd) {
53
60
  return new Promise((resolve) => {
54
61
  execFile('npm', ['install', ...specs], { cwd, timeout: 120_000 + 30_000 * specs.length, encoding: 'utf8' }, (err, _stdout, stderr) => {
@@ -1,6 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { createReadStream, existsSync, readFileSync } from 'node:fs';
3
- import { discoverPluginAssets } from "./plugin-discovery.js";
3
+ import { discoverPluginAssets, discoverRuntime } from "./plugin-discovery.js";
4
4
  import { getPlugins, readDisabled } from "./plugin-runtime.js";
5
5
  import { checkLatestVersion } from "./plugin-updates.js";
6
6
  const nmRoot = () => join(process.cwd(), 'node_modules');
@@ -12,7 +12,7 @@ export async function buildPluginListResponse(plugins, assets, disabled) {
12
12
  return {
13
13
  id: p.id,
14
14
  installedVersion: a?.version ?? p.version,
15
- latestVersion: a ? await checkLatestVersion(a.packageName) : null,
15
+ latestVersion: a && !a.local ? await checkLatestVersion(a.packageName) : null,
16
16
  packageName: a?.packageName ?? null,
17
17
  dependsOn: p.dependsOn,
18
18
  enabled: !disabled.has(p.id),
@@ -30,6 +30,13 @@ export async function registerPluginsApi(app) {
30
30
  const [plugins, assets, disabled] = await Promise.all([getPlugins(), discoverPluginAssets(), readDisabled()]);
31
31
  return buildPluginListResponse(plugins, assets, disabled);
32
32
  });
33
+ app.get('/api/runtime', async () => {
34
+ const runtime = await discoverRuntime();
35
+ if (!runtime)
36
+ return { installedVersion: null, latestVersion: null };
37
+ const latestVersion = runtime.local ? null : await checkLatestVersion(runtime.packageName);
38
+ return { installedVersion: runtime.installedVersion, latestVersion };
39
+ });
33
40
  app.get('/plugins/:id/:file', async (req, reply) => {
34
41
  const { id, file } = req.params;
35
42
  const key = ASSET_KEY[file];
package/dist/seeds.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { EntityManager } from '@mikro-orm/core';
2
- import type { PluginHooks } from './plugin-hooks.ts';
2
+ import { type PluginHooks } from './plugin-hooks.ts';
3
3
  export declare function validateSeeds(pluginId: string, list: {
4
4
  name: string;
5
5
  }[]): void;
package/dist/seeds.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { pluginCtx } from "./plugin-hooks.js";
1
2
  import { getLogger } from '@coffer-org/sdk/logger';
2
3
  const log = getLogger('seeds');
3
4
  export function validateSeeds(pluginId, list) {
@@ -16,7 +17,7 @@ export async function runSeeds({ em, plugins, hooks }) {
16
17
  const applied = new Set((await conn.execute(`SELECT name FROM _seeds WHERE plugin_id = ?`, [p.id])).map((r) => r.name));
17
18
  const pending = list.filter((s) => !applied.has(s.name));
18
19
  for (const s of pending) {
19
- await s.run({ em: em.fork() });
20
+ await s.run(pluginCtx(p.id, em.fork()));
20
21
  await conn.execute(`INSERT INTO _seeds (plugin_id, name) VALUES (?, ?)`, [p.id, s.name]);
21
22
  }
22
23
  if (pending.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/server",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -35,6 +35,7 @@
35
35
  "fastify": "^5.2.1",
36
36
  "open-graph-scraper": "^6.11.0",
37
37
  "pino": "^9.14.0",
38
+ "pino-pretty": "^13.1.3",
38
39
  "zod": "^4.4.3"
39
40
  },
40
41
  "optionalDependencies": {