@plank-cms/plank 0.26.1 → 0.27.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/admin/assets/index-BTElP7oS.css +2 -0
- package/dist/admin/assets/index-DZUtOtML.js +223 -0
- package/dist/admin/index.html +2 -2
- package/dist/index.js +2 -2
- package/dist/{server-7LSVEAFA.js → server-5JMBMGIN.js} +136 -39
- package/package.json +4 -4
- package/dist/admin/assets/index-BLYpKKlP.js +0 -223
- package/dist/admin/assets/index-BWA7n9y8.css +0 -2
package/dist/admin/index.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
href="https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap"
|
|
13
13
|
rel="stylesheet"
|
|
14
14
|
/>
|
|
15
|
-
<script type="module" crossorigin src="/admin/assets/index-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="/admin/assets/index-
|
|
15
|
+
<script type="module" crossorigin src="/admin/assets/index-DZUtOtML.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="/admin/assets/index-BTElP7oS.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { randomBytes } from "crypto";
|
|
|
7
7
|
import { resolve, join } from "path";
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import { execa } from "execa";
|
|
10
|
-
var PACKAGE_VERSION = "0.
|
|
10
|
+
var PACKAGE_VERSION = "0.27.1";
|
|
11
11
|
function generateSecret() {
|
|
12
12
|
return randomBytes(32).toString("hex");
|
|
13
13
|
}
|
|
@@ -106,7 +106,7 @@ import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
|
106
106
|
async function start() {
|
|
107
107
|
config({ path: resolve2(process.cwd(), ".env") });
|
|
108
108
|
process.env.PLANK_ADMIN_DIST = join2(dirname(fileURLToPath(import.meta.url)), "admin");
|
|
109
|
-
const { start: startServer } = await import("./server-
|
|
109
|
+
const { start: startServer } = await import("./server-5JMBMGIN.js");
|
|
110
110
|
await startServer();
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -651,7 +651,7 @@ import express from "express";
|
|
|
651
651
|
import cors from "cors";
|
|
652
652
|
import helmet from "helmet";
|
|
653
653
|
import { join as join4, dirname as dirname2 } from "path";
|
|
654
|
-
import { fileURLToPath as
|
|
654
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
655
655
|
|
|
656
656
|
// ../core/dist/routes/auth.js
|
|
657
657
|
import { Router } from "express";
|
|
@@ -2445,6 +2445,32 @@ async function getProvider() {
|
|
|
2445
2445
|
}
|
|
2446
2446
|
var upload = multer({ storage: multer.memoryStorage() });
|
|
2447
2447
|
|
|
2448
|
+
// ../core/dist/lib/publicAuthorSlug.js
|
|
2449
|
+
function slugifySegment(value) {
|
|
2450
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/^-+|-+$/g, "");
|
|
2451
|
+
}
|
|
2452
|
+
function baseSlugFromUser(input) {
|
|
2453
|
+
const fullName = [input.firstName, input.lastName].map((value) => value?.trim() ?? "").filter(Boolean).join(" ");
|
|
2454
|
+
const base = fullName || input.email.split("@")[0] || "author";
|
|
2455
|
+
return slugifySegment(base) || "author";
|
|
2456
|
+
}
|
|
2457
|
+
async function resolveUniquePublicAuthorSlug(input, excludeUserId) {
|
|
2458
|
+
const baseSlug = baseSlugFromUser(input);
|
|
2459
|
+
let slug = baseSlug;
|
|
2460
|
+
let suffix = 2;
|
|
2461
|
+
while (true) {
|
|
2462
|
+
const { rows } = await pool_default.query(`SELECT id
|
|
2463
|
+
FROM plank_users
|
|
2464
|
+
WHERE public_author_slug = $1
|
|
2465
|
+
AND ($2::text IS NULL OR id != $2)
|
|
2466
|
+
LIMIT 1`, [slug, excludeUserId ?? null]);
|
|
2467
|
+
if (!rows[0])
|
|
2468
|
+
return slug;
|
|
2469
|
+
slug = `${baseSlug}-${suffix}`;
|
|
2470
|
+
suffix += 1;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2448
2474
|
// ../core/dist/controllers/auth.js
|
|
2449
2475
|
var LoginSchema = z.object({
|
|
2450
2476
|
email: z.email(),
|
|
@@ -2456,6 +2482,8 @@ var Login2FASchema = z.object({
|
|
|
2456
2482
|
});
|
|
2457
2483
|
var RegisterSchema = z.object({
|
|
2458
2484
|
email: z.email(),
|
|
2485
|
+
firstName: z.string().trim().min(1).max(100),
|
|
2486
|
+
lastName: z.string().trim().min(1).max(100),
|
|
2459
2487
|
password: z.string().min(8)
|
|
2460
2488
|
});
|
|
2461
2489
|
var RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
|
|
@@ -2712,7 +2740,7 @@ async function register(req, res) {
|
|
|
2712
2740
|
res.status(400).json({ errors: flattenError(parsed.error, (i2) => i2.message) });
|
|
2713
2741
|
return;
|
|
2714
2742
|
}
|
|
2715
|
-
const { email, password } = parsed.data;
|
|
2743
|
+
const { email, firstName, lastName, password } = parsed.data;
|
|
2716
2744
|
const hashed = await bcrypt.hash(password, 12);
|
|
2717
2745
|
const { rows: roleRows } = await pool_default.query("SELECT id, name FROM plank_roles WHERE name = $1", ["Super Admin"]);
|
|
2718
2746
|
const superAdminRole = roleRows[0];
|
|
@@ -2721,7 +2749,10 @@ async function register(req, res) {
|
|
|
2721
2749
|
return;
|
|
2722
2750
|
}
|
|
2723
2751
|
const id = createId();
|
|
2724
|
-
|
|
2752
|
+
const publicAuthorSlug = await resolveUniquePublicAuthorSlug({ email, firstName, lastName });
|
|
2753
|
+
await pool_default.query(`INSERT INTO plank_users
|
|
2754
|
+
(id, email, password, role_id, first_name, last_name, public_author_slug)
|
|
2755
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, email, hashed, superAdminRole.id, firstName, lastName, publicAuthorSlug]);
|
|
2725
2756
|
res.status(201).json({ id, email });
|
|
2726
2757
|
}
|
|
2727
2758
|
|
|
@@ -3329,6 +3360,21 @@ function resolveLocalizedRow(row, ct, locale, fallbacks = []) {
|
|
|
3329
3360
|
function junctionTableName2(sourceTable, fieldName) {
|
|
3330
3361
|
return `_rel_${sourceTable}_${fieldName}`;
|
|
3331
3362
|
}
|
|
3363
|
+
function resolveManyToManyBinding(tableName, field) {
|
|
3364
|
+
const isInverse = (field.relationType ?? "many-to-one") === "many-to-many" && typeof field.relatedTable === "string" && field.relatedTable.length > 0 && typeof field.relatedField === "string" && field.relatedField.length > 0;
|
|
3365
|
+
if (isInverse) {
|
|
3366
|
+
return {
|
|
3367
|
+
junctionTable: junctionTableName2(field.relatedTable, field.relatedField),
|
|
3368
|
+
currentIdColumn: "target_id",
|
|
3369
|
+
relatedIdColumn: "source_id"
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
return {
|
|
3373
|
+
junctionTable: junctionTableName2(tableName, field.name),
|
|
3374
|
+
currentIdColumn: "source_id",
|
|
3375
|
+
relatedIdColumn: "target_id"
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3332
3378
|
function normalizeNavigationItems(value) {
|
|
3333
3379
|
if (!Array.isArray(value))
|
|
3334
3380
|
return value;
|
|
@@ -3366,12 +3412,12 @@ function normalizeNavigationFields(row, fields) {
|
|
|
3366
3412
|
return out;
|
|
3367
3413
|
}
|
|
3368
3414
|
async function syncManyToMany(entryId, tableName, field, targetIds) {
|
|
3369
|
-
const
|
|
3370
|
-
await pool_default.query(`DELETE FROM ${quoteIdentifier(
|
|
3415
|
+
const binding = resolveManyToManyBinding(tableName, field);
|
|
3416
|
+
await pool_default.query(`DELETE FROM ${quoteIdentifier(binding.junctionTable)} WHERE ${quoteIdentifier(binding.currentIdColumn)} = $1`, [entryId]);
|
|
3371
3417
|
if (targetIds.length === 0)
|
|
3372
3418
|
return;
|
|
3373
|
-
const
|
|
3374
|
-
await pool_default.query(`INSERT INTO ${quoteIdentifier(
|
|
3419
|
+
const tuples = targetIds.map((_3, i2) => binding.currentIdColumn === "source_id" ? `($1, $${i2 + 2})` : `($${i2 + 2}, $1)`).join(", ");
|
|
3420
|
+
await pool_default.query(`INSERT INTO ${quoteIdentifier(binding.junctionTable)} (source_id, target_id) VALUES ${tuples} ON CONFLICT DO NOTHING`, [entryId, ...targetIds]);
|
|
3375
3421
|
}
|
|
3376
3422
|
async function isContributorRole(roleId) {
|
|
3377
3423
|
if (!roleId)
|
|
@@ -3512,9 +3558,11 @@ async function loadManyToManyIds(entryId, tableName, fields) {
|
|
|
3512
3558
|
return {};
|
|
3513
3559
|
const result = {};
|
|
3514
3560
|
await Promise.all(mmFields.map(async (f2) => {
|
|
3515
|
-
const
|
|
3516
|
-
const { rows } = await pool_default.query(`SELECT
|
|
3517
|
-
|
|
3561
|
+
const binding = resolveManyToManyBinding(tableName, f2);
|
|
3562
|
+
const { rows } = await pool_default.query(`SELECT ${quoteIdentifier(binding.relatedIdColumn)} AS related_id
|
|
3563
|
+
FROM ${quoteIdentifier(binding.junctionTable)}
|
|
3564
|
+
WHERE ${quoteIdentifier(binding.currentIdColumn)} = $1`, [entryId]);
|
|
3565
|
+
result[f2.name] = rows.map((r2) => r2.related_id);
|
|
3518
3566
|
}));
|
|
3519
3567
|
return result;
|
|
3520
3568
|
}
|
|
@@ -3966,34 +4014,6 @@ var deleteEntry = async (req, res) => {
|
|
|
3966
4014
|
import bcrypt2 from "bcryptjs";
|
|
3967
4015
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
3968
4016
|
import { z as z4, flattenError as flattenError4 } from "zod";
|
|
3969
|
-
|
|
3970
|
-
// ../core/dist/lib/publicAuthorSlug.js
|
|
3971
|
-
function slugifySegment(value) {
|
|
3972
|
-
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/^-+|-+$/g, "");
|
|
3973
|
-
}
|
|
3974
|
-
function baseSlugFromUser(input) {
|
|
3975
|
-
const fullName = [input.firstName, input.lastName].map((value) => value?.trim() ?? "").filter(Boolean).join(" ");
|
|
3976
|
-
const base = fullName || input.email.split("@")[0] || "author";
|
|
3977
|
-
return slugifySegment(base) || "author";
|
|
3978
|
-
}
|
|
3979
|
-
async function resolveUniquePublicAuthorSlug(input, excludeUserId) {
|
|
3980
|
-
const baseSlug = baseSlugFromUser(input);
|
|
3981
|
-
let slug = baseSlug;
|
|
3982
|
-
let suffix = 2;
|
|
3983
|
-
while (true) {
|
|
3984
|
-
const { rows } = await pool_default.query(`SELECT id
|
|
3985
|
-
FROM plank_users
|
|
3986
|
-
WHERE public_author_slug = $1
|
|
3987
|
-
AND ($2::text IS NULL OR id != $2)
|
|
3988
|
-
LIMIT 1`, [slug, excludeUserId ?? null]);
|
|
3989
|
-
if (!rows[0])
|
|
3990
|
-
return slug;
|
|
3991
|
-
slug = `${baseSlug}-${suffix}`;
|
|
3992
|
-
suffix += 1;
|
|
3993
|
-
}
|
|
3994
|
-
}
|
|
3995
|
-
|
|
3996
|
-
// ../core/dist/controllers/users.js
|
|
3997
4017
|
var CreateUserSchema = z4.object({
|
|
3998
4018
|
email: z4.email(),
|
|
3999
4019
|
password: z4.string().min(8),
|
|
@@ -4907,6 +4927,82 @@ async function updateNamespaceSettings(req, res) {
|
|
|
4907
4927
|
res.json(maskSettings(namespace, updated));
|
|
4908
4928
|
}
|
|
4909
4929
|
|
|
4930
|
+
// ../core/dist/lib/version.js
|
|
4931
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
4932
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4933
|
+
var PACKAGE_NAME = "@plank-cms/plank";
|
|
4934
|
+
var CHANGELOG_BASE_URL = "https://github.com/plank-cms/plank/releases";
|
|
4935
|
+
var UPDATE_COMMAND = "npm run update";
|
|
4936
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
4937
|
+
var CACHE_TTL_MS = 1e3 * 60 * 30;
|
|
4938
|
+
var packageJsonUrl = new URL("../../package.json", import.meta.url);
|
|
4939
|
+
var cachedVersionCheck = null;
|
|
4940
|
+
function normalizeVersion(value) {
|
|
4941
|
+
return value.trim().replace(/^v/i, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
4942
|
+
}
|
|
4943
|
+
function compareVersions(a2, b3) {
|
|
4944
|
+
const left = normalizeVersion(a2);
|
|
4945
|
+
const right = normalizeVersion(b3);
|
|
4946
|
+
const maxLength = Math.max(left.length, right.length);
|
|
4947
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
4948
|
+
const leftPart = left[index] ?? 0;
|
|
4949
|
+
const rightPart = right[index] ?? 0;
|
|
4950
|
+
if (leftPart > rightPart)
|
|
4951
|
+
return 1;
|
|
4952
|
+
if (leftPart < rightPart)
|
|
4953
|
+
return -1;
|
|
4954
|
+
}
|
|
4955
|
+
return 0;
|
|
4956
|
+
}
|
|
4957
|
+
function getChangelogUrl(version) {
|
|
4958
|
+
return version ? `${CHANGELOG_BASE_URL}/tag/${version}` : CHANGELOG_BASE_URL;
|
|
4959
|
+
}
|
|
4960
|
+
async function readCurrentVersion() {
|
|
4961
|
+
const packageJsonPath = fileURLToPath2(packageJsonUrl);
|
|
4962
|
+
const raw = await readFile2(packageJsonPath, "utf8");
|
|
4963
|
+
const parsed = JSON.parse(raw);
|
|
4964
|
+
return parsed.version ?? "0.0.0";
|
|
4965
|
+
}
|
|
4966
|
+
async function getVersionCheck() {
|
|
4967
|
+
if (cachedVersionCheck && cachedVersionCheck.expiresAt > Date.now()) {
|
|
4968
|
+
return cachedVersionCheck.value;
|
|
4969
|
+
}
|
|
4970
|
+
const currentVersion = await readCurrentVersion();
|
|
4971
|
+
let latestVersion = null;
|
|
4972
|
+
try {
|
|
4973
|
+
const response = await fetch(REGISTRY_URL, {
|
|
4974
|
+
signal: AbortSignal.timeout(4e3),
|
|
4975
|
+
headers: {
|
|
4976
|
+
Accept: "application/json"
|
|
4977
|
+
}
|
|
4978
|
+
});
|
|
4979
|
+
if (response.ok) {
|
|
4980
|
+
const payload = await response.json();
|
|
4981
|
+
latestVersion = payload.version ?? null;
|
|
4982
|
+
}
|
|
4983
|
+
} catch {
|
|
4984
|
+
}
|
|
4985
|
+
const value = {
|
|
4986
|
+
currentVersion,
|
|
4987
|
+
latestVersion,
|
|
4988
|
+
updateAvailable: latestVersion ? compareVersions(latestVersion, currentVersion) > 0 : false,
|
|
4989
|
+
changelogUrl: getChangelogUrl(latestVersion),
|
|
4990
|
+
updateCommand: UPDATE_COMMAND,
|
|
4991
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4992
|
+
};
|
|
4993
|
+
cachedVersionCheck = {
|
|
4994
|
+
expiresAt: Date.now() + CACHE_TTL_MS,
|
|
4995
|
+
value
|
|
4996
|
+
};
|
|
4997
|
+
return value;
|
|
4998
|
+
}
|
|
4999
|
+
|
|
5000
|
+
// ../core/dist/controllers/version.js
|
|
5001
|
+
async function getVersionInfo(_req, res) {
|
|
5002
|
+
const versionInfo = await getVersionCheck();
|
|
5003
|
+
res.json(versionInfo);
|
|
5004
|
+
}
|
|
5005
|
+
|
|
4910
5006
|
// ../core/dist/routes/admin.js
|
|
4911
5007
|
var router2 = Router2();
|
|
4912
5008
|
router2.use(authenticate);
|
|
@@ -4962,6 +5058,7 @@ router2.delete("/media/:id", authorize("media:delete"), deleteMedia);
|
|
|
4962
5058
|
router2.get("/modes", getAppModes);
|
|
4963
5059
|
router2.get("/client-settings", getClientSettings);
|
|
4964
5060
|
router2.get("/editorial-mode", getEditorialMode);
|
|
5061
|
+
router2.get("/version", getVersionInfo);
|
|
4965
5062
|
router2.get("/settings/:namespace", authorize("settings:overview:read"), getNamespaceSettings);
|
|
4966
5063
|
router2.put("/settings/:namespace", authorize("settings:overview:write"), updateNamespaceSettings);
|
|
4967
5064
|
router2.get("/webhooks", authorize("settings:webhooks:read"), listWebhooks);
|
|
@@ -5675,7 +5772,7 @@ if (isDev) {
|
|
|
5675
5772
|
app.get("/admin/*path", (_req, res) => res.redirect(adminDevUrl));
|
|
5676
5773
|
app.get("/admin", (_req, res) => res.redirect(adminDevUrl));
|
|
5677
5774
|
} else {
|
|
5678
|
-
const adminDist = process.env.PLANK_ADMIN_DIST ?? join4(dirname2(
|
|
5775
|
+
const adminDist = process.env.PLANK_ADMIN_DIST ?? join4(dirname2(fileURLToPath3(import.meta.url)), "../public/admin");
|
|
5679
5776
|
app.use("/admin", express.static(adminDist));
|
|
5680
5777
|
app.get("/admin/*path", (_req, res) => res.sendFile(join4(adminDist, "index.html")));
|
|
5681
5778
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plank-cms/plank",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"description": "Self-hosted headless CMS. Deploy in minutes on your own infrastructure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/fs-extra": "^11.0.4",
|
|
57
57
|
"tsup": "^8.5.0",
|
|
58
|
-
"@plank-cms/db": "0.
|
|
59
|
-
"@plank-cms/schema": "0.
|
|
60
|
-
"@plank-cms/core": "0.
|
|
58
|
+
"@plank-cms/db": "0.27.1",
|
|
59
|
+
"@plank-cms/schema": "0.27.1",
|
|
60
|
+
"@plank-cms/core": "0.27.1"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsup",
|