@easy-cms/core 0.28.0 → 0.29.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.d.ts +70 -5
- package/dist/index.js +89 -5
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -238,6 +238,8 @@ interface EmailMessage {
|
|
|
238
238
|
* development. Throw to report a failure: the message is retried later.
|
|
239
239
|
*/
|
|
240
240
|
interface EmailAdapter {
|
|
241
|
+
/** What sends the email, e.g. `smtp`; shown to admins on the dashboard (System). */
|
|
242
|
+
readonly name?: string;
|
|
241
243
|
/** The sender when a message has none, e.g. `Easy CMS <no-reply@example.com>`. */
|
|
242
244
|
readonly from?: string | undefined;
|
|
243
245
|
send(message: EmailMessage & {
|
|
@@ -1726,8 +1728,17 @@ interface Endpoint {
|
|
|
1726
1728
|
*/
|
|
1727
1729
|
readonly handler: (request: EndpointRequest) => MaybePromise<unknown>;
|
|
1728
1730
|
}
|
|
1731
|
+
/** A plugin's package and version, listed for admins on the dashboard (System). */
|
|
1732
|
+
interface PluginInfo {
|
|
1733
|
+
/** Usually the package name, e.g. `@acme/easy-cms-plugin-stats`. */
|
|
1734
|
+
readonly name: string;
|
|
1735
|
+
readonly version?: string;
|
|
1736
|
+
}
|
|
1729
1737
|
/** Receives the config and returns a modified copy. Runs before validation. */
|
|
1730
|
-
type Plugin = (config: Config) => MaybePromise<Config
|
|
1738
|
+
type Plugin = ((config: Config) => MaybePromise<Config>) & {
|
|
1739
|
+
/** Set by `definePlugin(plugin, info)`. */
|
|
1740
|
+
readonly info?: PluginInfo;
|
|
1741
|
+
};
|
|
1731
1742
|
/** Holds a plugin's `PluginTypes` at the type level only: nothing is stored at runtime. */
|
|
1732
1743
|
declare const PLUGIN_TYPES: unique symbol;
|
|
1733
1744
|
/**
|
|
@@ -1749,17 +1760,18 @@ type TypedPlugin<T extends PluginTypes> = Plugin & {
|
|
|
1749
1760
|
readonly [PLUGIN_TYPES]?: T;
|
|
1750
1761
|
};
|
|
1751
1762
|
/**
|
|
1752
|
-
* Returns the plugin
|
|
1753
|
-
*
|
|
1763
|
+
* Returns the plugin with what it adds as types, so `cms.find()` and `CollectionDocument` know
|
|
1764
|
+
* the plugin's fields; `info` (its name and version) is listed for admins on the dashboard:
|
|
1754
1765
|
*
|
|
1755
1766
|
* ```ts
|
|
1756
1767
|
* export const colorPlugin = (options: { collections: readonly string[] }) =>
|
|
1757
1768
|
* definePlugin<{ fields: { posts: readonly [{ readonly name: 'color'; readonly type: 'text' }] } }>(
|
|
1758
1769
|
* (config) => ({ ...config, collections: … }),
|
|
1770
|
+
* { name: '@acme/easy-cms-plugin-color', version: '1.2.0' },
|
|
1759
1771
|
* )
|
|
1760
1772
|
* ```
|
|
1761
1773
|
*/
|
|
1762
|
-
declare function definePlugin<const T extends PluginTypes = Record<never, never>>(plugin: Plugin): TypedPlugin<T>;
|
|
1774
|
+
declare function definePlugin<const T extends PluginTypes = Record<never, never>>(plugin: Plugin, info?: PluginInfo): TypedPlugin<T>;
|
|
1763
1775
|
interface Config {
|
|
1764
1776
|
/** Signs sessions. At least 32 characters; read it from `process.env.EASY_CMS_SECRET`. */
|
|
1765
1777
|
readonly secret: string;
|
|
@@ -1831,6 +1843,8 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
|
|
|
1831
1843
|
readonly endpoints: readonly Endpoint[];
|
|
1832
1844
|
readonly commands: readonly CliCommand[];
|
|
1833
1845
|
readonly fieldTypes: readonly FieldTypeDefinition[];
|
|
1846
|
+
/** The config's plugins in order, with their `info` where they give one. */
|
|
1847
|
+
readonly installedPlugins: readonly Partial<PluginInfo>[];
|
|
1834
1848
|
}
|
|
1835
1849
|
/**
|
|
1836
1850
|
* Declares the Easy CMS config. Returns it unchanged, keeping literal types
|
|
@@ -2162,6 +2176,54 @@ interface AdminSchema {
|
|
|
2162
2176
|
dashboard: AdminWidgetRef[];
|
|
2163
2177
|
}
|
|
2164
2178
|
|
|
2179
|
+
/** Something an admin should look at, shown on the dashboard with a link to how to fix it. */
|
|
2180
|
+
type AdminAttention =
|
|
2181
|
+
/** Webhook deliveries that failed for good in the last 7 days; `url`: the one failing most. */
|
|
2182
|
+
{
|
|
2183
|
+
id: 'webhooks';
|
|
2184
|
+
count: number;
|
|
2185
|
+
url: string;
|
|
2186
|
+
}
|
|
2187
|
+
/** Emails waiting over an hour, or that failed for good in the last 7 days. */
|
|
2188
|
+
| {
|
|
2189
|
+
id: 'emails';
|
|
2190
|
+
count: number;
|
|
2191
|
+
}
|
|
2192
|
+
/** Scheduled publishing more than 10 minutes late: nothing runs `jobs/run`. */
|
|
2193
|
+
| {
|
|
2194
|
+
id: 'scheduled';
|
|
2195
|
+
count: number;
|
|
2196
|
+
}
|
|
2197
|
+
/** No `email` in the config: no forgotten-password links, invitations or form emails. */
|
|
2198
|
+
| {
|
|
2199
|
+
id: 'no-email';
|
|
2200
|
+
}
|
|
2201
|
+
/** In production without `serverURL`: links in emails can't be made. */
|
|
2202
|
+
| {
|
|
2203
|
+
id: 'no-server-url';
|
|
2204
|
+
};
|
|
2205
|
+
/** What the dashboard shows admins (`GET <api>/admin/status`). */
|
|
2206
|
+
interface AdminStatus {
|
|
2207
|
+
system: {
|
|
2208
|
+
/** `@easy-cms/core`'s version. */
|
|
2209
|
+
version: string;
|
|
2210
|
+
/** The database adapter, e.g. `sqlite` or `postgres`. */
|
|
2211
|
+
database: string;
|
|
2212
|
+
/** Where uploads are stored, e.g. `local` or `s3`. */
|
|
2213
|
+
storage: string;
|
|
2214
|
+
/** The email adapter's name, `custom` when it has none, or `null` without email. */
|
|
2215
|
+
email: string | null;
|
|
2216
|
+
/** The config's plugins, with their name and version when they give them. */
|
|
2217
|
+
plugins: {
|
|
2218
|
+
name?: string;
|
|
2219
|
+
version?: string;
|
|
2220
|
+
}[];
|
|
2221
|
+
/** Field types from packages (`fieldTypes`). */
|
|
2222
|
+
fieldTypes: string[];
|
|
2223
|
+
};
|
|
2224
|
+
attention: AdminAttention[];
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2165
2227
|
declare const SESSION_COOKIE = "ecms-session";
|
|
2166
2228
|
declare const CSRF_COOKIE = "ecms-csrf";
|
|
2167
2229
|
declare const CSRF_HEADER = "x-csrf-token";
|
|
@@ -2197,4 +2259,7 @@ declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
|
2197
2259
|
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
2198
2260
|
declare function validateConfig(config: Config): ConfigIssue[];
|
|
2199
2261
|
|
|
2200
|
-
|
|
2262
|
+
/** The version of `@easy-cms/core`, set when the package is built. */
|
|
2263
|
+
declare const VERSION: string;
|
|
2264
|
+
|
|
2265
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminSchema, type AdminStatus, type AdminViewAccess, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailField, type EmailMessage, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
package/dist/index.js
CHANGED
|
@@ -1142,7 +1142,8 @@ var ADMIN_ICONS = [
|
|
|
1142
1142
|
"chart-column",
|
|
1143
1143
|
"chart-line"
|
|
1144
1144
|
];
|
|
1145
|
-
function definePlugin(plugin) {
|
|
1145
|
+
function definePlugin(plugin, info) {
|
|
1146
|
+
if (info) Object.defineProperty(plugin, "info", { value: info, configurable: true });
|
|
1146
1147
|
return plugin;
|
|
1147
1148
|
}
|
|
1148
1149
|
function defineConfig(config) {
|
|
@@ -1304,13 +1305,13 @@ function generateSlugs(fields, data, localization = null) {
|
|
|
1304
1305
|
function mergeForUpdate(fields, existing, patch) {
|
|
1305
1306
|
const result = {};
|
|
1306
1307
|
for (const field of fields) {
|
|
1307
|
-
const
|
|
1308
|
+
const has2 = Object.hasOwn(patch, field.name);
|
|
1308
1309
|
const next = patch[field.name];
|
|
1309
1310
|
const prev = existing[field.name];
|
|
1310
|
-
if (field.type === "group" &&
|
|
1311
|
+
if (field.type === "group" && has2 && isPlainObject(next)) {
|
|
1311
1312
|
result[field.name] = mergeForUpdate(field.fields, isPlainObject(prev) ? prev : {}, next);
|
|
1312
1313
|
} else {
|
|
1313
|
-
result[field.name] =
|
|
1314
|
+
result[field.name] = has2 ? next : prev;
|
|
1314
1315
|
}
|
|
1315
1316
|
}
|
|
1316
1317
|
return result;
|
|
@@ -1541,6 +1542,7 @@ function normalizeSelect(field, raw, fail) {
|
|
|
1541
1542
|
function consoleEmail(options = {}) {
|
|
1542
1543
|
const sent = [];
|
|
1543
1544
|
const adapter = {
|
|
1545
|
+
name: "console",
|
|
1544
1546
|
from: options.from ?? "Easy CMS <no-reply@localhost>",
|
|
1545
1547
|
sent,
|
|
1546
1548
|
async send(message) {
|
|
@@ -2840,7 +2842,7 @@ async function resolveConfig(input) {
|
|
|
2840
2842
|
config = applied.config;
|
|
2841
2843
|
const issues = [...applied.issues, ...validateConfig(config)];
|
|
2842
2844
|
if (issues.length > 0) throw new ConfigError(issues);
|
|
2843
|
-
const { plugins
|
|
2845
|
+
const { plugins, ...rest } = config;
|
|
2844
2846
|
const result = {
|
|
2845
2847
|
...rest,
|
|
2846
2848
|
cors: config.cors ?? [],
|
|
@@ -2890,6 +2892,9 @@ async function resolveConfig(input) {
|
|
|
2890
2892
|
globals: config.globals ?? [],
|
|
2891
2893
|
endpoints: config.endpoints ?? [],
|
|
2892
2894
|
commands: config.commands ?? [],
|
|
2895
|
+
installedPlugins: (plugins ?? []).map(
|
|
2896
|
+
(plugin) => typeof plugin === "function" && plugin.info ? { ...plugin.info } : {}
|
|
2897
|
+
),
|
|
2893
2898
|
fieldTypes: config.fieldTypes ?? []
|
|
2894
2899
|
};
|
|
2895
2900
|
resolved.add(result);
|
|
@@ -5385,6 +5390,80 @@ async function widgets(cms, user) {
|
|
|
5385
5390
|
return out;
|
|
5386
5391
|
}
|
|
5387
5392
|
|
|
5393
|
+
// src/version.ts
|
|
5394
|
+
var VERSION = true ? "0.29.0" : "0.0.0-dev";
|
|
5395
|
+
|
|
5396
|
+
// src/rest/admin-status.ts
|
|
5397
|
+
var DAY = 864e5;
|
|
5398
|
+
var has = (cms, slug) => cms.config.collections.some((c) => c.slug === slug);
|
|
5399
|
+
async function adminStatus(cms) {
|
|
5400
|
+
const config = cms.config;
|
|
5401
|
+
const attention = [];
|
|
5402
|
+
const now = Date.now();
|
|
5403
|
+
const weekAgo = new Date(now - 7 * DAY).toISOString();
|
|
5404
|
+
if (has(cms, WEBHOOK_DELIVERIES)) {
|
|
5405
|
+
const failed = await cms.db.find({
|
|
5406
|
+
collection: WEBHOOK_DELIVERIES,
|
|
5407
|
+
where: { and: [{ state: { equals: "failed" } }, { updatedAt: { gte: weekAgo } }] },
|
|
5408
|
+
sort: ["-updatedAt"],
|
|
5409
|
+
limit: 200,
|
|
5410
|
+
page: 1
|
|
5411
|
+
});
|
|
5412
|
+
if (failed.totalDocs > 0) {
|
|
5413
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
5414
|
+
for (const row of failed.docs) {
|
|
5415
|
+
const url2 = String(row.url ?? "");
|
|
5416
|
+
byUrl.set(url2, (byUrl.get(url2) ?? 0) + 1);
|
|
5417
|
+
}
|
|
5418
|
+
const [url] = [...byUrl.entries()].sort((a, b) => b[1] - a[1])[0] ?? [""];
|
|
5419
|
+
attention.push({ id: "webhooks", count: failed.totalDocs, url });
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
if (has(cms, EMAIL_DELIVERIES)) {
|
|
5423
|
+
const count = await cms.db.count({
|
|
5424
|
+
collection: EMAIL_DELIVERIES,
|
|
5425
|
+
where: {
|
|
5426
|
+
or: [
|
|
5427
|
+
{ and: [{ state: { equals: "failed" } }, { updatedAt: { gte: weekAgo } }] },
|
|
5428
|
+
{
|
|
5429
|
+
and: [
|
|
5430
|
+
{ state: { equals: "pending" } },
|
|
5431
|
+
{ createdAt: { lte: new Date(now - 60 * 6e4).toISOString() } }
|
|
5432
|
+
]
|
|
5433
|
+
}
|
|
5434
|
+
]
|
|
5435
|
+
}
|
|
5436
|
+
});
|
|
5437
|
+
if (count > 0) attention.push({ id: "emails", count });
|
|
5438
|
+
}
|
|
5439
|
+
if (has(cms, SCHEDULED_JOBS)) {
|
|
5440
|
+
const count = await cms.db.count({
|
|
5441
|
+
collection: SCHEDULED_JOBS,
|
|
5442
|
+
where: {
|
|
5443
|
+
and: [
|
|
5444
|
+
{ state: { equals: "pending" } },
|
|
5445
|
+
{ runAt: { lte: new Date(now - 10 * 6e4).toISOString() } }
|
|
5446
|
+
]
|
|
5447
|
+
}
|
|
5448
|
+
});
|
|
5449
|
+
if (count > 0) attention.push({ id: "scheduled", count });
|
|
5450
|
+
}
|
|
5451
|
+
if (!config.email) attention.push({ id: "no-email" });
|
|
5452
|
+
if (!config.serverURL && process.env.NODE_ENV === "production")
|
|
5453
|
+
attention.push({ id: "no-server-url" });
|
|
5454
|
+
return {
|
|
5455
|
+
system: {
|
|
5456
|
+
version: VERSION,
|
|
5457
|
+
database: config.db.name,
|
|
5458
|
+
storage: config.upload.storage?.name ?? "local",
|
|
5459
|
+
email: config.email ? config.email.name ?? "custom" : null,
|
|
5460
|
+
plugins: config.installedPlugins.map((p) => ({ ...p })),
|
|
5461
|
+
fieldTypes: config.fieldTypes.map((t) => t.name)
|
|
5462
|
+
},
|
|
5463
|
+
attention
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
|
|
5388
5467
|
// src/rest/filter-options.ts
|
|
5389
5468
|
function fieldAt(fields, path) {
|
|
5390
5469
|
let current = fields;
|
|
@@ -5771,6 +5850,10 @@ async function route(cms, ctx, method, segments, options) {
|
|
|
5771
5850
|
return { body: new Response(null, { status: 304, headers }) };
|
|
5772
5851
|
return { body: new Response(file.body, { headers }) };
|
|
5773
5852
|
}
|
|
5853
|
+
if (second === "status" && third === void 0) {
|
|
5854
|
+
if (ctx.user.role !== "admin" || ctx.user.apiKey) throw new ForbiddenError();
|
|
5855
|
+
return { body: await adminStatus(cms) };
|
|
5856
|
+
}
|
|
5774
5857
|
if (second === "scheduled" && third === void 0)
|
|
5775
5858
|
return { body: await cms.upcomingJobs({ user: ctx.user, overrideAccess: false }) };
|
|
5776
5859
|
const [, , collection3, id2, extra] = segments;
|
|
@@ -6382,6 +6465,7 @@ export {
|
|
|
6382
6465
|
SchemaError,
|
|
6383
6466
|
TooManyRequestsError,
|
|
6384
6467
|
UnauthorizedError,
|
|
6468
|
+
VERSION,
|
|
6385
6469
|
ValidationError,
|
|
6386
6470
|
WEBHOOK_EVENTS,
|
|
6387
6471
|
anyone,
|