@easy-cms/core 0.16.0 → 0.17.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 +26 -2
- package/dist/index.js +109 -34
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1275,10 +1275,17 @@ interface EndpointRequest {
|
|
|
1275
1275
|
interface Endpoint {
|
|
1276
1276
|
/**
|
|
1277
1277
|
* Path under the API, e.g. `/seo/generate` or `/stats/:collection`. The first segment must
|
|
1278
|
-
* not be a collection slug or one of `users`, `globals`, `admin`, `jobs`.
|
|
1278
|
+
* not be a collection slug or one of `users`, `globals`, `admin`, `jobs`. With `root`, the
|
|
1279
|
+
* path from the site's root, outside the API and the admin.
|
|
1279
1280
|
*/
|
|
1280
1281
|
readonly path: string;
|
|
1281
1282
|
readonly method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
1283
|
+
/**
|
|
1284
|
+
* Serve the path from the site's root instead of under `routes.api`, e.g. `/robots.txt`.
|
|
1285
|
+
* Only the standalone server (`easy-cms serve`) serves these; a Nuxt or Next.js app owns its
|
|
1286
|
+
* root, so plugins also offer a helper to use in the app's own route. Default false.
|
|
1287
|
+
*/
|
|
1288
|
+
readonly root?: boolean;
|
|
1282
1289
|
/**
|
|
1283
1290
|
* Returns a `Response`, or a value that is sent as JSON. Throw an Easy CMS error
|
|
1284
1291
|
* (`UnauthorizedError`, `ForbiddenError`, `ValidationError`, `NotFoundError`) for error responses.
|
|
@@ -1388,6 +1395,16 @@ declare const MEDIA = "media";
|
|
|
1388
1395
|
/** Collections Easy CMS uses internally. Not exposed over REST or in the admin UI. */
|
|
1389
1396
|
declare const INTERNAL_COLLECTIONS: ReadonlySet<string>;
|
|
1390
1397
|
|
|
1398
|
+
/**
|
|
1399
|
+
* A string that is the same for configs with the same structure: collections, fields, options
|
|
1400
|
+
* and every other plain value. Functions count only by where they are, not by their code, since
|
|
1401
|
+
* bundlers rename variables differently each time they compile the same source.
|
|
1402
|
+
*
|
|
1403
|
+
* Frameworks may load the config module more than once (Next.js bundles it into each server
|
|
1404
|
+
* layer), which gives different objects for the same config; this tells those apart from edits.
|
|
1405
|
+
*/
|
|
1406
|
+
declare function configSignature(config: Config): string;
|
|
1407
|
+
|
|
1391
1408
|
interface CopyProgress {
|
|
1392
1409
|
readonly table: string;
|
|
1393
1410
|
readonly rows: number;
|
|
@@ -1689,6 +1706,13 @@ interface RestHandlerOptions {
|
|
|
1689
1706
|
type RestHandler = (request: Request) => Promise<Response>;
|
|
1690
1707
|
/** Creates the REST API as a Web-standard `(Request) => Response` handler. */
|
|
1691
1708
|
declare function createRestHandler<C extends Config>(instance: EasyCMS<C>, options?: RestHandlerOptions): RestHandler;
|
|
1709
|
+
/**
|
|
1710
|
+
* Serves the endpoints marked `root: true` (e.g. `/robots.txt`), with the same auth, CSRF and
|
|
1711
|
+
* errors as the REST API. Resolves to `undefined` when no root endpoint has the request's path,
|
|
1712
|
+
* so a server can go on to its other routes. Nuxt and Next.js apps own their root and don't
|
|
1713
|
+
* use it; the standalone server does.
|
|
1714
|
+
*/
|
|
1715
|
+
declare function createRootEndpointHandler<C extends Config>(instance: EasyCMS<C>, options?: Pick<RestHandlerOptions, 'getClientIp'>): (request: Request) => Promise<Response | undefined>;
|
|
1692
1716
|
declare function readCookie(request: Request, name: string): string | undefined;
|
|
1693
1717
|
|
|
1694
1718
|
/** Best-effort English singular: "categories" → "category". */
|
|
@@ -1705,4 +1729,4 @@ declare const BUILTIN_COLLECTIONS: readonly ["users", "media"];
|
|
|
1705
1729
|
/** Checks a config and returns every problem found. An empty array means it is valid. */
|
|
1706
1730
|
declare function validateConfig(config: Config): ConfigIssue[];
|
|
1707
1731
|
|
|
1708
|
-
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminSchema, 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 CollectionAccess, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailField, type Endpoint, type EndpointRequest, FIELD_TYPES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, 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, PayloadTooLargeError, type Plugin, 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 StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, UnauthorizedError, type UpdateInput, type UploadConfig, type UploadField, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
|
1732
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminSchema, 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 CollectionAccess, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_TOKEN_EXPIRATION, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, EXTENSIONS, EasyCMS, EasyCMSError, type EmailField, type Endpoint, type EndpointRequest, FIELD_TYPES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, 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, PayloadTooLargeError, type Plugin, 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 StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, UnauthorizedError, type UpdateInput, type UploadConfig, type UploadField, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
package/dist/index.js
CHANGED
|
@@ -915,6 +915,31 @@ function defineConfig(config) {
|
|
|
915
915
|
return config;
|
|
916
916
|
}
|
|
917
917
|
|
|
918
|
+
// src/config-signature.ts
|
|
919
|
+
function configSignature(config) {
|
|
920
|
+
const known = signatures.get(config);
|
|
921
|
+
if (known !== void 0) return known;
|
|
922
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
923
|
+
const signature = JSON.stringify(config, (_key, value) => {
|
|
924
|
+
if (typeof value === "function") return "fn";
|
|
925
|
+
if (typeof value === "object" && value !== null) {
|
|
926
|
+
if (seen.has(value)) return "[circular]";
|
|
927
|
+
seen.add(value);
|
|
928
|
+
if (!Array.isArray(value)) {
|
|
929
|
+
const sorted = {};
|
|
930
|
+
for (const key2 of Object.keys(value).sort())
|
|
931
|
+
sorted[key2] = value[key2];
|
|
932
|
+
seen.add(sorted);
|
|
933
|
+
return sorted;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return value;
|
|
937
|
+
});
|
|
938
|
+
signatures.set(config, signature);
|
|
939
|
+
return signature;
|
|
940
|
+
}
|
|
941
|
+
var signatures = /* @__PURE__ */ new WeakMap();
|
|
942
|
+
|
|
918
943
|
// src/copy.ts
|
|
919
944
|
var BATCH = 200;
|
|
920
945
|
async function copyDatabase(source, target, options = {}) {
|
|
@@ -1991,18 +2016,31 @@ function validateEndpoints(config, collectionSlugs, add) {
|
|
|
1991
2016
|
add(path, "must be an object { path, method, handler }");
|
|
1992
2017
|
return;
|
|
1993
2018
|
}
|
|
1994
|
-
const { path: route2, method, handler } = endpoint;
|
|
2019
|
+
const { path: route2, method, handler, root } = endpoint;
|
|
2020
|
+
if (root !== void 0 && typeof root !== "boolean")
|
|
2021
|
+
add(`${path}.root`, "must be true or false");
|
|
1995
2022
|
const segments = typeof route2 === "string" ? route2.split("/").slice(1) : [];
|
|
1996
2023
|
if (typeof route2 !== "string" || !route2.startsWith("/") || segments.length === 0 || !segments.every((s) => ENDPOINT_SEGMENT.test(s))) {
|
|
1997
2024
|
add(`${path}.path`, `must be a path like "/seo/generate" (got ${JSON.stringify(route2)})`);
|
|
1998
2025
|
} else {
|
|
1999
|
-
const
|
|
2000
|
-
if (
|
|
2026
|
+
const first = segments[0];
|
|
2027
|
+
if (first.startsWith(":")) {
|
|
2001
2028
|
add(`${path}.path`, "must start with a fixed segment, not a parameter");
|
|
2002
|
-
} else if (
|
|
2029
|
+
} else if (root === true) {
|
|
2030
|
+
const taken = [
|
|
2031
|
+
config.routes?.api ?? "/api/cms",
|
|
2032
|
+
`/${(config.admin?.path ?? "/admin").replace(/^\/+|\/+$/g, "")}`,
|
|
2033
|
+
"/healthz"
|
|
2034
|
+
].map((p) => p.replace(/\/+$/, "")).find((p) => route2 === p || route2.startsWith(`${p}/`) || p.startsWith(`${route2}/`));
|
|
2035
|
+
if (taken)
|
|
2036
|
+
add(
|
|
2037
|
+
`${path}.path`,
|
|
2038
|
+
`"${route2}" is served by ${taken === "/healthz" ? "the standalone server" : `"${taken}"`}`
|
|
2039
|
+
);
|
|
2040
|
+
} else if (RESERVED_ENDPOINT_ROOTS.has(first) || collectionSlugs.has(first)) {
|
|
2003
2041
|
add(
|
|
2004
2042
|
`${path}.path`,
|
|
2005
|
-
`"/${
|
|
2043
|
+
`"/${first}" is used by the built-in API`,
|
|
2006
2044
|
'start the path with your plugin or feature name, e.g. "/seo/generate"'
|
|
2007
2045
|
);
|
|
2008
2046
|
}
|
|
@@ -2016,7 +2054,7 @@ function validateEndpoints(config, collectionSlugs, add) {
|
|
|
2016
2054
|
}
|
|
2017
2055
|
if (typeof handler !== "function") add(`${path}.handler`, "must be a function");
|
|
2018
2056
|
if (typeof route2 === "string" && typeof verb === "string") {
|
|
2019
|
-
const key2 = `${verb} ${route2.replace(/:[A-Za-z_][A-Za-z0-9_]*/g, ":")}`;
|
|
2057
|
+
const key2 = `${root === true ? "root " : ""}${verb} ${route2.replace(/:[A-Za-z_][A-Za-z0-9_]*/g, ":")}`;
|
|
2020
2058
|
if (seen.has(key2)) add(`${path}.path`, `duplicate endpoint ${verb.toUpperCase()} ${route2}`);
|
|
2021
2059
|
seen.add(key2);
|
|
2022
2060
|
}
|
|
@@ -4333,6 +4371,42 @@ function createRestHandler(instance, options = {}) {
|
|
|
4333
4371
|
const cms = instance;
|
|
4334
4372
|
const basePath = (options.basePath ?? cms.config.routes.api).replace(/\/+$/, "");
|
|
4335
4373
|
const production = process.env.NODE_ENV === "production";
|
|
4374
|
+
return withCors(cms, (request) => {
|
|
4375
|
+
const url = new URL(request.url);
|
|
4376
|
+
const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
|
|
4377
|
+
return respond(
|
|
4378
|
+
cms,
|
|
4379
|
+
request,
|
|
4380
|
+
url,
|
|
4381
|
+
inside ? url.pathname.slice(basePath.length) : null,
|
|
4382
|
+
false,
|
|
4383
|
+
options,
|
|
4384
|
+
production
|
|
4385
|
+
);
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
function createRootEndpointHandler(instance, options = {}) {
|
|
4389
|
+
const cms = instance;
|
|
4390
|
+
const production = process.env.NODE_ENV === "production";
|
|
4391
|
+
const handler = withCors(cms, (request) => {
|
|
4392
|
+
const url = new URL(request.url);
|
|
4393
|
+
return respond(cms, request, url, url.pathname, true, options, production);
|
|
4394
|
+
});
|
|
4395
|
+
return async (request) => {
|
|
4396
|
+
let segments;
|
|
4397
|
+
try {
|
|
4398
|
+
segments = pathSegments(new URL(request.url).pathname);
|
|
4399
|
+
} catch {
|
|
4400
|
+
return void 0;
|
|
4401
|
+
}
|
|
4402
|
+
const matched = cms.config.endpoints.some((e) => e.root && matchPath(e.path, segments));
|
|
4403
|
+
return matched ? handler(request) : void 0;
|
|
4404
|
+
};
|
|
4405
|
+
}
|
|
4406
|
+
function pathSegments(path) {
|
|
4407
|
+
return path.split("/").filter(Boolean).map((s) => decodeURIComponent(s));
|
|
4408
|
+
}
|
|
4409
|
+
function withCors(cms, handle) {
|
|
4336
4410
|
return async (request) => {
|
|
4337
4411
|
const cors = corsHeaders(cms, request);
|
|
4338
4412
|
if (request.method.toUpperCase() === "OPTIONS") {
|
|
@@ -4340,31 +4414,29 @@ function createRestHandler(instance, options = {}) {
|
|
|
4340
4414
|
}
|
|
4341
4415
|
return withHeaders(await handle(request), cors);
|
|
4342
4416
|
};
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
return errorResponse(cms, error, headers, production);
|
|
4367
|
-
}
|
|
4417
|
+
}
|
|
4418
|
+
async function respond(cms, request, url, path, root, options, production) {
|
|
4419
|
+
const headers = new Headers({
|
|
4420
|
+
"content-type": "application/json; charset=utf-8",
|
|
4421
|
+
"cache-control": "no-store",
|
|
4422
|
+
"x-content-type-options": "nosniff"
|
|
4423
|
+
});
|
|
4424
|
+
try {
|
|
4425
|
+
if (path === null) throw new HttpError("Not found", 404);
|
|
4426
|
+
const segments = pathSegments(path);
|
|
4427
|
+
const { token, via } = readToken(request);
|
|
4428
|
+
const user = token ? await cms.auth.verify(token) : null;
|
|
4429
|
+
if (!user && via === "bearer" && token?.startsWith("ecms_"))
|
|
4430
|
+
throw new UnauthorizedError("Invalid or expired API key");
|
|
4431
|
+
const ctx = { request, url, user, via: user ? via : null, token, headers };
|
|
4432
|
+
const method = request.method.toUpperCase();
|
|
4433
|
+
if (method !== "GET" && method !== "HEAD") checkCsrf(cms, ctx);
|
|
4434
|
+
const result = root ? await customEndpoint(cms, ctx, method, segments, true) : await customEndpoint(cms, ctx, method, segments, false) ?? await route(cms, ctx, method, segments, options);
|
|
4435
|
+
if (!result) throw new HttpError("Not found", 404);
|
|
4436
|
+
if (result.body instanceof Response) return result.body;
|
|
4437
|
+
return new Response(JSON.stringify(result.body), { status: result.status ?? 200, headers });
|
|
4438
|
+
} catch (error) {
|
|
4439
|
+
return errorResponse(cms, error, headers, production);
|
|
4368
4440
|
}
|
|
4369
4441
|
}
|
|
4370
4442
|
var CORS_METHODS = "GET, HEAD, POST, PATCH, PUT, DELETE";
|
|
@@ -4397,9 +4469,10 @@ function withHeaders(response, extra) {
|
|
|
4397
4469
|
headers
|
|
4398
4470
|
});
|
|
4399
4471
|
}
|
|
4400
|
-
async function customEndpoint(cms, ctx, method, segments) {
|
|
4472
|
+
async function customEndpoint(cms, ctx, method, segments, root) {
|
|
4401
4473
|
let pathMatched = false;
|
|
4402
|
-
const
|
|
4474
|
+
const own = cms.config.endpoints.filter((e) => !!e.root === root);
|
|
4475
|
+
const endpoints = [...own].sort((a, b) => fixedSegments(b) - fixedSegments(a));
|
|
4403
4476
|
for (const endpoint of endpoints) {
|
|
4404
4477
|
const params = matchPath(endpoint.path, segments);
|
|
4405
4478
|
if (!params) continue;
|
|
@@ -4417,7 +4490,7 @@ async function customEndpoint(cms, ctx, method, segments) {
|
|
|
4417
4490
|
return { body: body === void 0 ? null : body };
|
|
4418
4491
|
}
|
|
4419
4492
|
if (pathMatched) {
|
|
4420
|
-
const allow =
|
|
4493
|
+
const allow = own.filter((e) => matchPath(e.path, segments)).map((e) => e.method.toUpperCase());
|
|
4421
4494
|
throw methodNotAllowed(ctx, [...new Set(allow)].join(", "));
|
|
4422
4495
|
}
|
|
4423
4496
|
return void 0;
|
|
@@ -5092,10 +5165,12 @@ export {
|
|
|
5092
5165
|
anyone,
|
|
5093
5166
|
applyDefaults,
|
|
5094
5167
|
applyPlugins,
|
|
5168
|
+
configSignature,
|
|
5095
5169
|
consoleLogger,
|
|
5096
5170
|
copyDatabase,
|
|
5097
5171
|
createEasyCMS,
|
|
5098
5172
|
createRestHandler,
|
|
5173
|
+
createRootEndpointHandler,
|
|
5099
5174
|
defineConfig,
|
|
5100
5175
|
fillMissing,
|
|
5101
5176
|
findConfigFile,
|