@plank-cms/plank 0.27.3 → 0.28.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/README.md +7 -0
- package/dist/admin/assets/index-BhxOFNX5.css +2 -0
- package/dist/admin/assets/{index-CrCKvadB.js → index-CdtvxQpy.js} +52 -52
- package/dist/admin/index.html +2 -2
- package/dist/index.js +120 -22
- package/dist/migrations/033_plank_addons.sql +22 -0
- package/dist/{server-5JMBMGIN.js → server-DONLO5CM.js} +733 -13
- package/package.json +4 -4
- package/dist/admin/assets/index-BTElP7oS.css +0 -2
|
@@ -37,6 +37,8 @@ var DEFAULT_ROLE_PERMISSIONS = {
|
|
|
37
37
|
"media:read",
|
|
38
38
|
"media:write",
|
|
39
39
|
"media:delete",
|
|
40
|
+
"addons:read",
|
|
41
|
+
"addons:write",
|
|
40
42
|
"settings:overview:read",
|
|
41
43
|
"settings:users:read",
|
|
42
44
|
"settings:users:write",
|
|
@@ -650,8 +652,8 @@ function validate(contentType, payload) {
|
|
|
650
652
|
import express from "express";
|
|
651
653
|
import cors from "cors";
|
|
652
654
|
import helmet from "helmet";
|
|
653
|
-
import { join as
|
|
654
|
-
import { fileURLToPath as
|
|
655
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
656
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
655
657
|
|
|
656
658
|
// ../core/dist/routes/auth.js
|
|
657
659
|
import { Router } from "express";
|
|
@@ -4875,6 +4877,10 @@ function maskSettings(namespace, settings) {
|
|
|
4875
4877
|
}
|
|
4876
4878
|
async function getNamespaceSettings(req, res) {
|
|
4877
4879
|
const { namespace } = req.params;
|
|
4880
|
+
if (namespace.startsWith("addon:")) {
|
|
4881
|
+
res.status(403).json({ error: "Addon settings must be accessed through the add-ons API" });
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4878
4884
|
const settings = await getSettings(namespace);
|
|
4879
4885
|
res.json(maskSettings(namespace, settings));
|
|
4880
4886
|
}
|
|
@@ -4903,6 +4909,10 @@ async function getEditorialMode(_req, res) {
|
|
|
4903
4909
|
}
|
|
4904
4910
|
async function updateNamespaceSettings(req, res) {
|
|
4905
4911
|
const { namespace } = req.params;
|
|
4912
|
+
if (namespace.startsWith("addon:")) {
|
|
4913
|
+
res.status(403).json({ error: "Addon settings must be accessed through the add-ons API" });
|
|
4914
|
+
return;
|
|
4915
|
+
}
|
|
4906
4916
|
const incoming = req.body;
|
|
4907
4917
|
if (typeof incoming !== "object" || Array.isArray(incoming)) {
|
|
4908
4918
|
res.status(400).json({ error: "Body must be a flat key-value object" });
|
|
@@ -4929,13 +4939,16 @@ async function updateNamespaceSettings(req, res) {
|
|
|
4929
4939
|
|
|
4930
4940
|
// ../core/dist/lib/version.js
|
|
4931
4941
|
import { readFile as readFile2 } from "fs/promises";
|
|
4942
|
+
import { join as join4 } from "path";
|
|
4932
4943
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4933
4944
|
var PACKAGE_NAME = "@plank-cms/plank";
|
|
4934
4945
|
var CHANGELOG_BASE_URL = "https://github.com/plank-cms/plank/releases";
|
|
4935
|
-
var UPDATE_COMMAND = "npm run update";
|
|
4936
4946
|
var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
4937
4947
|
var CACHE_TTL_MS = 1e3 * 60 * 30;
|
|
4938
|
-
var
|
|
4948
|
+
var packageJsonUrls = [
|
|
4949
|
+
new URL("../../package.json", import.meta.url),
|
|
4950
|
+
new URL("../package.json", import.meta.url)
|
|
4951
|
+
];
|
|
4939
4952
|
var cachedVersionCheck = null;
|
|
4940
4953
|
function normalizeVersion(value) {
|
|
4941
4954
|
return value.trim().replace(/^v/i, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -4957,17 +4970,62 @@ function compareVersions(a2, b3) {
|
|
|
4957
4970
|
function getChangelogUrl(version) {
|
|
4958
4971
|
return version ? `${CHANGELOG_BASE_URL}/tag/${version}` : CHANGELOG_BASE_URL;
|
|
4959
4972
|
}
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4973
|
+
function getUpdateCommandForPackageManager(packageManager) {
|
|
4974
|
+
return packageManager === "pnpm" ? "pnpm run update" : "npm run update";
|
|
4975
|
+
}
|
|
4976
|
+
async function detectProjectPackageManager() {
|
|
4977
|
+
try {
|
|
4978
|
+
const raw = await readFile2(join4(process.cwd(), "package.json"), "utf8");
|
|
4979
|
+
const parsed = JSON.parse(raw);
|
|
4980
|
+
if (parsed.packageManager?.startsWith("pnpm@") || parsed.packageManager === "pnpm") {
|
|
4981
|
+
return "pnpm";
|
|
4982
|
+
}
|
|
4983
|
+
if (parsed.packageManager?.startsWith("npm@") || parsed.packageManager === "npm") {
|
|
4984
|
+
return "npm";
|
|
4985
|
+
}
|
|
4986
|
+
} catch {
|
|
4987
|
+
return await detectPackageManagerFromLockfiles();
|
|
4988
|
+
}
|
|
4989
|
+
return await detectPackageManagerFromLockfiles();
|
|
4990
|
+
}
|
|
4991
|
+
async function hasLockfile(filename) {
|
|
4992
|
+
try {
|
|
4993
|
+
await readFile2(join4(process.cwd(), filename), "utf8");
|
|
4994
|
+
return true;
|
|
4995
|
+
} catch {
|
|
4996
|
+
return false;
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
async function detectPackageManagerFromLockfiles() {
|
|
5000
|
+
if (await hasLockfile("pnpm-lock.yaml")) {
|
|
5001
|
+
return "pnpm";
|
|
5002
|
+
}
|
|
5003
|
+
if (await hasLockfile("package-lock.json")) {
|
|
5004
|
+
return "npm";
|
|
5005
|
+
}
|
|
5006
|
+
return null;
|
|
5007
|
+
}
|
|
5008
|
+
async function getCurrentVersion() {
|
|
5009
|
+
for (const packageJsonUrl of packageJsonUrls) {
|
|
5010
|
+
try {
|
|
5011
|
+
const packageJsonPath = fileURLToPath2(packageJsonUrl);
|
|
5012
|
+
const raw = await readFile2(packageJsonPath, "utf8");
|
|
5013
|
+
const parsed = JSON.parse(raw);
|
|
5014
|
+
if (parsed.version) {
|
|
5015
|
+
return parsed.version;
|
|
5016
|
+
}
|
|
5017
|
+
} catch {
|
|
5018
|
+
continue;
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
return "0.0.0";
|
|
4965
5022
|
}
|
|
4966
5023
|
async function getVersionCheck() {
|
|
4967
5024
|
if (cachedVersionCheck && cachedVersionCheck.expiresAt > Date.now()) {
|
|
4968
5025
|
return cachedVersionCheck.value;
|
|
4969
5026
|
}
|
|
4970
|
-
const currentVersion = await
|
|
5027
|
+
const currentVersion = await getCurrentVersion();
|
|
5028
|
+
const packageManager = await detectProjectPackageManager();
|
|
4971
5029
|
let latestVersion = null;
|
|
4972
5030
|
try {
|
|
4973
5031
|
const response = await fetch(REGISTRY_URL, {
|
|
@@ -4981,13 +5039,14 @@ async function getVersionCheck() {
|
|
|
4981
5039
|
latestVersion = payload.version ?? null;
|
|
4982
5040
|
}
|
|
4983
5041
|
} catch {
|
|
5042
|
+
latestVersion = null;
|
|
4984
5043
|
}
|
|
4985
5044
|
const value = {
|
|
4986
5045
|
currentVersion,
|
|
4987
5046
|
latestVersion,
|
|
4988
5047
|
updateAvailable: latestVersion ? compareVersions(latestVersion, currentVersion) > 0 : false,
|
|
4989
5048
|
changelogUrl: getChangelogUrl(latestVersion),
|
|
4990
|
-
updateCommand:
|
|
5049
|
+
updateCommand: getUpdateCommandForPackageManager(packageManager),
|
|
4991
5050
|
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4992
5051
|
};
|
|
4993
5052
|
cachedVersionCheck = {
|
|
@@ -5003,6 +5062,657 @@ async function getVersionInfo(_req, res) {
|
|
|
5003
5062
|
res.json(versionInfo);
|
|
5004
5063
|
}
|
|
5005
5064
|
|
|
5065
|
+
// ../core/dist/controllers/addons.js
|
|
5066
|
+
import { z as z9 } from "zod";
|
|
5067
|
+
|
|
5068
|
+
// ../core/dist/lib/addons.js
|
|
5069
|
+
import { access, readFile as readFile3 } from "fs/promises";
|
|
5070
|
+
import { createRequire } from "module";
|
|
5071
|
+
import { dirname as dirname2, join as join5, resolve } from "path";
|
|
5072
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5073
|
+
import { z as z8 } from "zod";
|
|
5074
|
+
var ADDON_PACKAGE_PREFIX = "@plank-cms/addon-";
|
|
5075
|
+
function getHostRequire() {
|
|
5076
|
+
return createRequire(join5(process.cwd(), "package.json"));
|
|
5077
|
+
}
|
|
5078
|
+
var addonSlotSchema = z8.object({
|
|
5079
|
+
id: z8.string().min(1),
|
|
5080
|
+
title: z8.string().min(1),
|
|
5081
|
+
order: z8.number().int().optional()
|
|
5082
|
+
});
|
|
5083
|
+
var addonManifestSchema = z8.object({
|
|
5084
|
+
id: z8.string().min(1),
|
|
5085
|
+
packageName: z8.string().min(1),
|
|
5086
|
+
name: z8.string().min(1),
|
|
5087
|
+
version: z8.string().min(1),
|
|
5088
|
+
plankRange: z8.string().min(1),
|
|
5089
|
+
description: z8.string().optional(),
|
|
5090
|
+
settingsNamespace: z8.string().min(1).optional(),
|
|
5091
|
+
slots: z8.object({
|
|
5092
|
+
dashboardWidgets: z8.array(addonSlotSchema).optional(),
|
|
5093
|
+
addonsSections: z8.array(addonSlotSchema).optional()
|
|
5094
|
+
}),
|
|
5095
|
+
admin: z8.object({
|
|
5096
|
+
entry: z8.string().min(1)
|
|
5097
|
+
}).optional()
|
|
5098
|
+
});
|
|
5099
|
+
var addonAdminFieldSchema = z8.discriminatedUnion("type", [
|
|
5100
|
+
z8.object({
|
|
5101
|
+
key: z8.string().min(1),
|
|
5102
|
+
type: z8.literal("contentTypesMultiSelect"),
|
|
5103
|
+
label: z8.string().min(1),
|
|
5104
|
+
description: z8.string().min(1),
|
|
5105
|
+
defaultValue: z8.array(z8.string())
|
|
5106
|
+
}),
|
|
5107
|
+
z8.object({
|
|
5108
|
+
key: z8.string().min(1),
|
|
5109
|
+
type: z8.literal("number"),
|
|
5110
|
+
label: z8.string().min(1),
|
|
5111
|
+
description: z8.string().min(1),
|
|
5112
|
+
min: z8.number(),
|
|
5113
|
+
defaultValue: z8.number()
|
|
5114
|
+
})
|
|
5115
|
+
]);
|
|
5116
|
+
var addonAdminModuleSchema = z8.object({
|
|
5117
|
+
addonId: z8.string().min(1),
|
|
5118
|
+
title: z8.string().min(1),
|
|
5119
|
+
description: z8.string().min(1),
|
|
5120
|
+
settingsNamespace: z8.string().min(1),
|
|
5121
|
+
checks: z8.array(z8.object({
|
|
5122
|
+
id: z8.string().min(1),
|
|
5123
|
+
label: z8.string().min(1),
|
|
5124
|
+
description: z8.string().min(1)
|
|
5125
|
+
})),
|
|
5126
|
+
settings: z8.object({
|
|
5127
|
+
title: z8.string().min(1),
|
|
5128
|
+
description: z8.string().min(1),
|
|
5129
|
+
fields: z8.array(addonAdminFieldSchema)
|
|
5130
|
+
})
|
|
5131
|
+
});
|
|
5132
|
+
function normalizeSlot(slot) {
|
|
5133
|
+
return {
|
|
5134
|
+
id: slot.id,
|
|
5135
|
+
title: slot.title,
|
|
5136
|
+
order: slot.order ?? 100
|
|
5137
|
+
};
|
|
5138
|
+
}
|
|
5139
|
+
function compareSlotOrder(left, right) {
|
|
5140
|
+
if ((left.order ?? 100) !== (right.order ?? 100)) {
|
|
5141
|
+
return (left.order ?? 100) - (right.order ?? 100);
|
|
5142
|
+
}
|
|
5143
|
+
return left.id.localeCompare(right.id);
|
|
5144
|
+
}
|
|
5145
|
+
function normalizeAddonSlots(manifest) {
|
|
5146
|
+
const dashboardWidgets = (manifest.slots.dashboardWidgets ?? []).map(normalizeSlot).sort(compareSlotOrder);
|
|
5147
|
+
const addonsSections = manifest.admin?.entry ? (manifest.slots.addonsSections ?? []).map(normalizeSlot).sort(compareSlotOrder) : [];
|
|
5148
|
+
return {
|
|
5149
|
+
dashboardWidgets,
|
|
5150
|
+
addonsSections
|
|
5151
|
+
};
|
|
5152
|
+
}
|
|
5153
|
+
function createFallbackAddonId(packageName) {
|
|
5154
|
+
return packageName.startsWith(ADDON_PACKAGE_PREFIX) ? packageName.slice(ADDON_PACKAGE_PREFIX.length) : packageName;
|
|
5155
|
+
}
|
|
5156
|
+
async function pathExists(path) {
|
|
5157
|
+
try {
|
|
5158
|
+
await access(path);
|
|
5159
|
+
return true;
|
|
5160
|
+
} catch {
|
|
5161
|
+
return false;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
async function resolvePackageJsonPath(packageName) {
|
|
5165
|
+
const hostRequire = getHostRequire();
|
|
5166
|
+
try {
|
|
5167
|
+
return hostRequire.resolve(`${packageName}/package.json`);
|
|
5168
|
+
} catch {
|
|
5169
|
+
try {
|
|
5170
|
+
const entryPath = hostRequire.resolve(packageName);
|
|
5171
|
+
let currentDir = dirname2(entryPath);
|
|
5172
|
+
for (; ; ) {
|
|
5173
|
+
const candidate = join5(currentDir, "package.json");
|
|
5174
|
+
if (await pathExists(candidate)) {
|
|
5175
|
+
return candidate;
|
|
5176
|
+
}
|
|
5177
|
+
const parentDir = dirname2(currentDir);
|
|
5178
|
+
if (parentDir === currentDir)
|
|
5179
|
+
return null;
|
|
5180
|
+
currentDir = parentDir;
|
|
5181
|
+
}
|
|
5182
|
+
} catch {
|
|
5183
|
+
return null;
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
async function resolvePackageRoot(packageName) {
|
|
5188
|
+
const packageJsonPath = await resolvePackageJsonPath(packageName);
|
|
5189
|
+
if (packageJsonPath)
|
|
5190
|
+
return dirname2(packageJsonPath);
|
|
5191
|
+
try {
|
|
5192
|
+
const manifestUrl = import.meta.resolve(`${packageName}/plank`);
|
|
5193
|
+
let currentDir = dirname2(fileURLToPath3(manifestUrl));
|
|
5194
|
+
for (; ; ) {
|
|
5195
|
+
const candidate = join5(currentDir, "package.json");
|
|
5196
|
+
if (await pathExists(candidate)) {
|
|
5197
|
+
return currentDir;
|
|
5198
|
+
}
|
|
5199
|
+
const parentDir = dirname2(currentDir);
|
|
5200
|
+
if (parentDir === currentDir)
|
|
5201
|
+
return null;
|
|
5202
|
+
currentDir = parentDir;
|
|
5203
|
+
}
|
|
5204
|
+
} catch {
|
|
5205
|
+
return null;
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
async function readInstalledPackageJson(packageName) {
|
|
5209
|
+
const packageJsonPath = await resolvePackageJsonPath(packageName);
|
|
5210
|
+
if (!packageJsonPath)
|
|
5211
|
+
return null;
|
|
5212
|
+
try {
|
|
5213
|
+
const raw = await readFile3(packageJsonPath, "utf8");
|
|
5214
|
+
return JSON.parse(raw);
|
|
5215
|
+
} catch {
|
|
5216
|
+
return null;
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
async function readHostPackageJson() {
|
|
5220
|
+
const raw = await readFile3(join5(process.cwd(), "package.json"), "utf8");
|
|
5221
|
+
return JSON.parse(raw);
|
|
5222
|
+
}
|
|
5223
|
+
function listDeclaredAddonPackages(packageJson) {
|
|
5224
|
+
return Array.from(/* @__PURE__ */ new Set([
|
|
5225
|
+
...Object.keys(packageJson.dependencies ?? {}),
|
|
5226
|
+
...Object.keys(packageJson.optionalDependencies ?? {})
|
|
5227
|
+
])).filter((packageName) => packageName.startsWith(ADDON_PACKAGE_PREFIX)).sort();
|
|
5228
|
+
}
|
|
5229
|
+
function normalizeVersion2(value) {
|
|
5230
|
+
const [major = 0, minor = 0, patch = 0] = value.trim().replace(/^v/i, "").split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
5231
|
+
return [major, minor, patch];
|
|
5232
|
+
}
|
|
5233
|
+
function compareVersions2(left, right) {
|
|
5234
|
+
const leftParts = normalizeVersion2(left);
|
|
5235
|
+
const rightParts = normalizeVersion2(right);
|
|
5236
|
+
for (let index = 0; index < 3; index += 1) {
|
|
5237
|
+
if (leftParts[index] > rightParts[index])
|
|
5238
|
+
return 1;
|
|
5239
|
+
if (leftParts[index] < rightParts[index])
|
|
5240
|
+
return -1;
|
|
5241
|
+
}
|
|
5242
|
+
return 0;
|
|
5243
|
+
}
|
|
5244
|
+
function buildCaretUpperBound(version) {
|
|
5245
|
+
const [major, minor, patch] = normalizeVersion2(version);
|
|
5246
|
+
if (major > 0)
|
|
5247
|
+
return `${major + 1}.0.0`;
|
|
5248
|
+
if (minor > 0)
|
|
5249
|
+
return `0.${minor + 1}.0`;
|
|
5250
|
+
return `0.0.${patch + 1}`;
|
|
5251
|
+
}
|
|
5252
|
+
function buildTildeUpperBound(version) {
|
|
5253
|
+
const [major, minor] = normalizeVersion2(version);
|
|
5254
|
+
return `${major}.${minor + 1}.0`;
|
|
5255
|
+
}
|
|
5256
|
+
function satisfiesComparator(version, comparator) {
|
|
5257
|
+
const value = comparator.trim();
|
|
5258
|
+
if (!value)
|
|
5259
|
+
return true;
|
|
5260
|
+
if (value.startsWith(">="))
|
|
5261
|
+
return compareVersions2(version, value.slice(2)) >= 0;
|
|
5262
|
+
if (value.startsWith("<="))
|
|
5263
|
+
return compareVersions2(version, value.slice(2)) <= 0;
|
|
5264
|
+
if (value.startsWith(">"))
|
|
5265
|
+
return compareVersions2(version, value.slice(1)) > 0;
|
|
5266
|
+
if (value.startsWith("<"))
|
|
5267
|
+
return compareVersions2(version, value.slice(1)) < 0;
|
|
5268
|
+
if (value.startsWith("^")) {
|
|
5269
|
+
const lower = value.slice(1);
|
|
5270
|
+
return compareVersions2(version, lower) >= 0 && compareVersions2(version, buildCaretUpperBound(lower)) < 0;
|
|
5271
|
+
}
|
|
5272
|
+
if (value.startsWith("~")) {
|
|
5273
|
+
const lower = value.slice(1);
|
|
5274
|
+
return compareVersions2(version, lower) >= 0 && compareVersions2(version, buildTildeUpperBound(lower)) < 0;
|
|
5275
|
+
}
|
|
5276
|
+
return compareVersions2(version, value) === 0;
|
|
5277
|
+
}
|
|
5278
|
+
function satisfiesVersionRange(version, range) {
|
|
5279
|
+
const normalizedRange = range.trim();
|
|
5280
|
+
if (!normalizedRange || normalizedRange === "*")
|
|
5281
|
+
return true;
|
|
5282
|
+
return normalizedRange.split("||").some((part) => part.trim().split(/\s+/).every((token) => satisfiesComparator(version, token)));
|
|
5283
|
+
}
|
|
5284
|
+
async function loadAddonManifest(packageName) {
|
|
5285
|
+
const module = await import(`${packageName}/plank`);
|
|
5286
|
+
const parsed = addonManifestSchema.safeParse(module?.manifest);
|
|
5287
|
+
if (!parsed.success) {
|
|
5288
|
+
throw new Error(parsed.error.issues.map((issue) => issue.message).join(", "));
|
|
5289
|
+
}
|
|
5290
|
+
if (parsed.data.packageName !== packageName) {
|
|
5291
|
+
throw new Error(`Manifest packageName mismatch for ${packageName}`);
|
|
5292
|
+
}
|
|
5293
|
+
return parsed.data;
|
|
5294
|
+
}
|
|
5295
|
+
async function loadAddonAdminModule(packageName) {
|
|
5296
|
+
const module = await import(`${packageName}/admin`);
|
|
5297
|
+
const candidate = module?.adminModule ?? module?.default;
|
|
5298
|
+
const parsed = addonAdminModuleSchema.safeParse(candidate);
|
|
5299
|
+
if (!parsed.success) {
|
|
5300
|
+
throw new Error(parsed.error.issues.map((issue) => issue.message).join(", "));
|
|
5301
|
+
}
|
|
5302
|
+
return parsed.data;
|
|
5303
|
+
}
|
|
5304
|
+
async function loadAddonServerModule(packageName) {
|
|
5305
|
+
const module = await import(`${packageName}/server`);
|
|
5306
|
+
const candidate = module?.serverModule ?? module?.default;
|
|
5307
|
+
if (!candidate || typeof candidate.runAction !== "function") {
|
|
5308
|
+
throw new Error(`Invalid server module for ${packageName}`);
|
|
5309
|
+
}
|
|
5310
|
+
return candidate;
|
|
5311
|
+
}
|
|
5312
|
+
async function resolveAddonAdminEntryPath(packageName) {
|
|
5313
|
+
const [manifest, packageRoot] = await Promise.all([
|
|
5314
|
+
loadAddonManifest(packageName),
|
|
5315
|
+
resolvePackageRoot(packageName)
|
|
5316
|
+
]);
|
|
5317
|
+
if (!manifest.admin?.entry || !packageRoot)
|
|
5318
|
+
return null;
|
|
5319
|
+
const entryPath = resolve(packageRoot, manifest.admin.entry);
|
|
5320
|
+
if (!entryPath.startsWith(packageRoot)) {
|
|
5321
|
+
throw new Error(`Invalid admin entry path for ${packageName}`);
|
|
5322
|
+
}
|
|
5323
|
+
if (!await pathExists(entryPath)) {
|
|
5324
|
+
throw new Error(`Admin entry file not found for ${packageName}: ${entryPath}`);
|
|
5325
|
+
}
|
|
5326
|
+
return entryPath;
|
|
5327
|
+
}
|
|
5328
|
+
async function discoverAddon(packageName, coreVersion) {
|
|
5329
|
+
const packageJson = await readInstalledPackageJson(packageName);
|
|
5330
|
+
try {
|
|
5331
|
+
const manifest = await loadAddonManifest(packageName);
|
|
5332
|
+
const compatible = satisfiesVersionRange(coreVersion, manifest.plankRange);
|
|
5333
|
+
return {
|
|
5334
|
+
id: manifest.id,
|
|
5335
|
+
packageName,
|
|
5336
|
+
name: manifest.name,
|
|
5337
|
+
version: manifest.version,
|
|
5338
|
+
plankRange: manifest.plankRange,
|
|
5339
|
+
description: manifest.description ?? packageJson?.description ?? null,
|
|
5340
|
+
installed: true,
|
|
5341
|
+
enabled: false,
|
|
5342
|
+
compatible,
|
|
5343
|
+
hasAdminUi: Boolean(manifest.admin?.entry),
|
|
5344
|
+
settingsNamespace: manifest.settingsNamespace ?? null,
|
|
5345
|
+
slots: normalizeAddonSlots(manifest)
|
|
5346
|
+
};
|
|
5347
|
+
} catch (error) {
|
|
5348
|
+
const fallbackId = createFallbackAddonId(packageName);
|
|
5349
|
+
const message = error instanceof Error ? error.message : "Unknown manifest error";
|
|
5350
|
+
console.warn(`[plank/addons] Skipping invalid add-on manifest for ${packageName}: ${message}`);
|
|
5351
|
+
return {
|
|
5352
|
+
id: fallbackId,
|
|
5353
|
+
packageName,
|
|
5354
|
+
name: packageJson?.name ?? fallbackId,
|
|
5355
|
+
version: packageJson?.version ?? null,
|
|
5356
|
+
plankRange: null,
|
|
5357
|
+
description: packageJson?.description ?? null,
|
|
5358
|
+
installed: true,
|
|
5359
|
+
enabled: false,
|
|
5360
|
+
compatible: false,
|
|
5361
|
+
hasAdminUi: false,
|
|
5362
|
+
settingsNamespace: null,
|
|
5363
|
+
slots: {
|
|
5364
|
+
dashboardWidgets: [],
|
|
5365
|
+
addonsSections: []
|
|
5366
|
+
}
|
|
5367
|
+
};
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
async function syncInstalledAddons() {
|
|
5371
|
+
const hostPackageJson = await readHostPackageJson();
|
|
5372
|
+
const packageNames = listDeclaredAddonPackages(hostPackageJson);
|
|
5373
|
+
const coreVersion = await getCurrentVersion();
|
|
5374
|
+
const discovered = (await Promise.all(packageNames.map((packageName) => discoverAddon(packageName, coreVersion)))).filter((addon) => addon !== null);
|
|
5375
|
+
const client = await pool_default.connect();
|
|
5376
|
+
try {
|
|
5377
|
+
await client.query("BEGIN");
|
|
5378
|
+
for (const addon of discovered) {
|
|
5379
|
+
await client.query(`INSERT INTO plank_addons (
|
|
5380
|
+
id,
|
|
5381
|
+
package_name,
|
|
5382
|
+
name,
|
|
5383
|
+
version,
|
|
5384
|
+
plank_range,
|
|
5385
|
+
description,
|
|
5386
|
+
installed,
|
|
5387
|
+
compatible,
|
|
5388
|
+
has_admin_ui,
|
|
5389
|
+
settings_namespace,
|
|
5390
|
+
slots_json
|
|
5391
|
+
)
|
|
5392
|
+
VALUES ($1, $2, $3, $4, $5, $6, TRUE, $7, $8, $9, $10::jsonb)
|
|
5393
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
5394
|
+
package_name = EXCLUDED.package_name,
|
|
5395
|
+
name = EXCLUDED.name,
|
|
5396
|
+
version = EXCLUDED.version,
|
|
5397
|
+
plank_range = EXCLUDED.plank_range,
|
|
5398
|
+
description = EXCLUDED.description,
|
|
5399
|
+
installed = EXCLUDED.installed,
|
|
5400
|
+
compatible = EXCLUDED.compatible,
|
|
5401
|
+
has_admin_ui = EXCLUDED.has_admin_ui,
|
|
5402
|
+
settings_namespace = EXCLUDED.settings_namespace,
|
|
5403
|
+
slots_json = EXCLUDED.slots_json,
|
|
5404
|
+
updated_at = NOW()`, [
|
|
5405
|
+
addon.id,
|
|
5406
|
+
addon.packageName,
|
|
5407
|
+
addon.name,
|
|
5408
|
+
addon.version,
|
|
5409
|
+
addon.plankRange,
|
|
5410
|
+
addon.description,
|
|
5411
|
+
addon.compatible,
|
|
5412
|
+
addon.hasAdminUi,
|
|
5413
|
+
addon.settingsNamespace,
|
|
5414
|
+
JSON.stringify(addon.slots)
|
|
5415
|
+
]);
|
|
5416
|
+
}
|
|
5417
|
+
if (packageNames.length > 0) {
|
|
5418
|
+
await client.query(`UPDATE plank_addons
|
|
5419
|
+
SET installed = FALSE,
|
|
5420
|
+
compatible = FALSE,
|
|
5421
|
+
has_admin_ui = FALSE,
|
|
5422
|
+
slots_json = '{}'::jsonb,
|
|
5423
|
+
updated_at = NOW()
|
|
5424
|
+
WHERE package_name LIKE $1
|
|
5425
|
+
AND package_name <> ALL($2::text[])`, [`${ADDON_PACKAGE_PREFIX}%`, packageNames]);
|
|
5426
|
+
} else {
|
|
5427
|
+
await client.query(`UPDATE plank_addons
|
|
5428
|
+
SET installed = FALSE,
|
|
5429
|
+
compatible = FALSE,
|
|
5430
|
+
has_admin_ui = FALSE,
|
|
5431
|
+
slots_json = '{}'::jsonb,
|
|
5432
|
+
updated_at = NOW()
|
|
5433
|
+
WHERE package_name LIKE $1`, [`${ADDON_PACKAGE_PREFIX}%`]);
|
|
5434
|
+
}
|
|
5435
|
+
await client.query("COMMIT");
|
|
5436
|
+
} catch (error) {
|
|
5437
|
+
await client.query("ROLLBACK");
|
|
5438
|
+
throw error;
|
|
5439
|
+
} finally {
|
|
5440
|
+
client.release();
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
function normalizeSlotsFromRow(value) {
|
|
5444
|
+
return {
|
|
5445
|
+
dashboardWidgets: Array.isArray(value?.dashboardWidgets) ? value.dashboardWidgets : [],
|
|
5446
|
+
addonsSections: Array.isArray(value?.addonsSections) ? value.addonsSections : []
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5449
|
+
function mapAddonRow(row) {
|
|
5450
|
+
return {
|
|
5451
|
+
id: row.id,
|
|
5452
|
+
packageName: row.package_name,
|
|
5453
|
+
name: row.name,
|
|
5454
|
+
version: row.version ?? "",
|
|
5455
|
+
...row.description ? { description: row.description } : {},
|
|
5456
|
+
installed: row.installed,
|
|
5457
|
+
enabled: row.enabled,
|
|
5458
|
+
compatible: row.compatible,
|
|
5459
|
+
hasAdminUi: row.has_admin_ui,
|
|
5460
|
+
...row.settings_namespace ? { settingsNamespace: row.settings_namespace } : {},
|
|
5461
|
+
...row.has_admin_ui ? { adminUrl: `/admin/add-ons/${row.id}` } : {}
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
async function listAddonRows() {
|
|
5465
|
+
const { rows } = await pool_default.query(`SELECT
|
|
5466
|
+
id,
|
|
5467
|
+
package_name,
|
|
5468
|
+
name,
|
|
5469
|
+
version,
|
|
5470
|
+
plank_range,
|
|
5471
|
+
description,
|
|
5472
|
+
installed,
|
|
5473
|
+
enabled,
|
|
5474
|
+
compatible,
|
|
5475
|
+
has_admin_ui,
|
|
5476
|
+
settings_namespace,
|
|
5477
|
+
slots_json
|
|
5478
|
+
FROM plank_addons
|
|
5479
|
+
ORDER BY name ASC, id ASC`);
|
|
5480
|
+
return rows;
|
|
5481
|
+
}
|
|
5482
|
+
async function getAddonRow(id) {
|
|
5483
|
+
const { rows } = await pool_default.query(`SELECT
|
|
5484
|
+
id,
|
|
5485
|
+
package_name,
|
|
5486
|
+
name,
|
|
5487
|
+
version,
|
|
5488
|
+
plank_range,
|
|
5489
|
+
description,
|
|
5490
|
+
installed,
|
|
5491
|
+
enabled,
|
|
5492
|
+
compatible,
|
|
5493
|
+
has_admin_ui,
|
|
5494
|
+
settings_namespace,
|
|
5495
|
+
slots_json
|
|
5496
|
+
FROM plank_addons
|
|
5497
|
+
WHERE id = $1`, [id]);
|
|
5498
|
+
return rows[0] ?? null;
|
|
5499
|
+
}
|
|
5500
|
+
async function getAddonAdminModule(id) {
|
|
5501
|
+
const addon = await getAddonRow(id);
|
|
5502
|
+
if (!addon || !addon.installed || !addon.has_admin_ui)
|
|
5503
|
+
return null;
|
|
5504
|
+
try {
|
|
5505
|
+
return await loadAddonAdminModule(addon.package_name);
|
|
5506
|
+
} catch (error) {
|
|
5507
|
+
const message = error instanceof Error ? error.message : "Unknown admin module error";
|
|
5508
|
+
console.warn(`[plank/addons] Failed to load admin module for ${addon.package_name}: ${message}`);
|
|
5509
|
+
return null;
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
async function getAddonAdminEntryPath(id) {
|
|
5513
|
+
const addon = await getAddonRow(id);
|
|
5514
|
+
if (!addon || !addon.installed || !addon.has_admin_ui)
|
|
5515
|
+
return null;
|
|
5516
|
+
try {
|
|
5517
|
+
return await resolveAddonAdminEntryPath(addon.package_name);
|
|
5518
|
+
} catch (error) {
|
|
5519
|
+
const message = error instanceof Error ? error.message : "Unknown admin entry error";
|
|
5520
|
+
console.warn(`[plank/addons] Failed to resolve admin entry for ${addon.package_name}: ${message}`);
|
|
5521
|
+
return null;
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
async function runAddonServerAction(id, action, input) {
|
|
5525
|
+
const addon = await getAddonRow(id);
|
|
5526
|
+
if (!addon) {
|
|
5527
|
+
throw new Error("Addon not found");
|
|
5528
|
+
}
|
|
5529
|
+
if (!addon.installed) {
|
|
5530
|
+
throw new Error("Addon is not installed");
|
|
5531
|
+
}
|
|
5532
|
+
if (!addon.enabled) {
|
|
5533
|
+
throw new Error("Addon is disabled");
|
|
5534
|
+
}
|
|
5535
|
+
if (!addon.compatible) {
|
|
5536
|
+
throw new Error("Addon is not compatible with this Plank version");
|
|
5537
|
+
}
|
|
5538
|
+
const serverModule = await loadAddonServerModule(addon.package_name);
|
|
5539
|
+
return serverModule.runAction({
|
|
5540
|
+
action,
|
|
5541
|
+
input,
|
|
5542
|
+
addon: {
|
|
5543
|
+
id: addon.id,
|
|
5544
|
+
packageName: addon.package_name,
|
|
5545
|
+
settingsNamespace: addon.settings_namespace
|
|
5546
|
+
},
|
|
5547
|
+
context: {
|
|
5548
|
+
db: {
|
|
5549
|
+
query: pool_default.query.bind(pool_default)
|
|
5550
|
+
},
|
|
5551
|
+
getSettings,
|
|
5552
|
+
findAllContentTypes,
|
|
5553
|
+
findContentTypeBySlug,
|
|
5554
|
+
quoteIdentifier
|
|
5555
|
+
}
|
|
5556
|
+
});
|
|
5557
|
+
}
|
|
5558
|
+
async function updateAddonEnabled(id, enabled) {
|
|
5559
|
+
const { rows } = await pool_default.query(`UPDATE plank_addons
|
|
5560
|
+
SET enabled = $2, updated_at = NOW()
|
|
5561
|
+
WHERE id = $1
|
|
5562
|
+
RETURNING
|
|
5563
|
+
id,
|
|
5564
|
+
package_name,
|
|
5565
|
+
name,
|
|
5566
|
+
version,
|
|
5567
|
+
plank_range,
|
|
5568
|
+
description,
|
|
5569
|
+
installed,
|
|
5570
|
+
enabled,
|
|
5571
|
+
compatible,
|
|
5572
|
+
has_admin_ui,
|
|
5573
|
+
settings_namespace,
|
|
5574
|
+
slots_json`, [id, enabled]);
|
|
5575
|
+
return rows[0] ?? null;
|
|
5576
|
+
}
|
|
5577
|
+
async function buildAdminAddonsRegistry() {
|
|
5578
|
+
const rows = await listAddonRows();
|
|
5579
|
+
const enabledRows = rows.filter((row) => row.installed && row.enabled && row.compatible);
|
|
5580
|
+
return {
|
|
5581
|
+
addons: rows.map(mapAddonRow),
|
|
5582
|
+
slots: {
|
|
5583
|
+
dashboardWidgets: enabledRows.flatMap((row) => normalizeSlotsFromRow(row.slots_json).dashboardWidgets.map((slot) => ({
|
|
5584
|
+
addonId: row.id,
|
|
5585
|
+
slotId: slot.id,
|
|
5586
|
+
title: slot.title,
|
|
5587
|
+
order: slot.order ?? 100
|
|
5588
|
+
}))),
|
|
5589
|
+
addonsSections: enabledRows.flatMap((row) => normalizeSlotsFromRow(row.slots_json).addonsSections.map((slot) => ({
|
|
5590
|
+
addonId: row.id,
|
|
5591
|
+
slotId: slot.id,
|
|
5592
|
+
title: slot.title,
|
|
5593
|
+
order: slot.order ?? 100
|
|
5594
|
+
})))
|
|
5595
|
+
}
|
|
5596
|
+
};
|
|
5597
|
+
}
|
|
5598
|
+
|
|
5599
|
+
// ../core/dist/controllers/addons.js
|
|
5600
|
+
function mapAddon(row) {
|
|
5601
|
+
return {
|
|
5602
|
+
id: row.id,
|
|
5603
|
+
packageName: row.package_name,
|
|
5604
|
+
name: row.name,
|
|
5605
|
+
version: row.version ?? "",
|
|
5606
|
+
...row.description ? { description: row.description } : {},
|
|
5607
|
+
installed: row.installed,
|
|
5608
|
+
enabled: row.enabled,
|
|
5609
|
+
compatible: row.compatible,
|
|
5610
|
+
hasAdminUi: row.has_admin_ui,
|
|
5611
|
+
...row.settings_namespace ? { settingsNamespace: row.settings_namespace } : {}
|
|
5612
|
+
};
|
|
5613
|
+
}
|
|
5614
|
+
async function getSettingsAddon(id) {
|
|
5615
|
+
const addon = await getAddonRow(id);
|
|
5616
|
+
if (!addon)
|
|
5617
|
+
return { error: "Addon not found" };
|
|
5618
|
+
if (!addon.settings_namespace)
|
|
5619
|
+
return { error: "Addon does not expose settings" };
|
|
5620
|
+
return { addon };
|
|
5621
|
+
}
|
|
5622
|
+
async function getAddonsRegistry(_req, res) {
|
|
5623
|
+
const registry = await buildAdminAddonsRegistry();
|
|
5624
|
+
res.json(registry);
|
|
5625
|
+
}
|
|
5626
|
+
async function listAddons(_req, res) {
|
|
5627
|
+
const addons = await listAddonRows();
|
|
5628
|
+
res.json(addons.map(mapAddon));
|
|
5629
|
+
}
|
|
5630
|
+
async function enableAddon(req, res) {
|
|
5631
|
+
const addon = await getAddonRow(req.params.id);
|
|
5632
|
+
if (!addon) {
|
|
5633
|
+
res.status(404).json({ error: "Addon not found" });
|
|
5634
|
+
return;
|
|
5635
|
+
}
|
|
5636
|
+
if (!addon.installed) {
|
|
5637
|
+
res.status(409).json({ error: "Addon is not installed" });
|
|
5638
|
+
return;
|
|
5639
|
+
}
|
|
5640
|
+
if (!addon.compatible) {
|
|
5641
|
+
res.status(409).json({ error: "Addon is not compatible with this Plank version" });
|
|
5642
|
+
return;
|
|
5643
|
+
}
|
|
5644
|
+
const updated = await updateAddonEnabled(req.params.id, true);
|
|
5645
|
+
res.json(mapAddon(updated));
|
|
5646
|
+
}
|
|
5647
|
+
async function disableAddon(req, res) {
|
|
5648
|
+
const addon = await getAddonRow(req.params.id);
|
|
5649
|
+
if (!addon) {
|
|
5650
|
+
res.status(404).json({ error: "Addon not found" });
|
|
5651
|
+
return;
|
|
5652
|
+
}
|
|
5653
|
+
const updated = await updateAddonEnabled(req.params.id, false);
|
|
5654
|
+
res.json(mapAddon(updated));
|
|
5655
|
+
}
|
|
5656
|
+
async function getAddonSettings(req, res) {
|
|
5657
|
+
const result = await getSettingsAddon(req.params.id);
|
|
5658
|
+
if ("error" in result) {
|
|
5659
|
+
res.status(result.error === "Addon not found" ? 404 : 400).json({ error: result.error });
|
|
5660
|
+
return;
|
|
5661
|
+
}
|
|
5662
|
+
const settings = await getSettings(result.addon.settings_namespace);
|
|
5663
|
+
res.json(settings);
|
|
5664
|
+
}
|
|
5665
|
+
async function getAddonAdminModuleDefinition(req, res) {
|
|
5666
|
+
const addon = await getAddonAdminModule(req.params.id);
|
|
5667
|
+
if (!addon) {
|
|
5668
|
+
res.status(404).json({ error: "Addon admin module not found" });
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
res.json(addon);
|
|
5672
|
+
}
|
|
5673
|
+
async function getAddonAdminEntry(req, res) {
|
|
5674
|
+
const entryPath = await getAddonAdminEntryPath(req.params.id);
|
|
5675
|
+
if (!entryPath) {
|
|
5676
|
+
res.status(404).json({ error: "Addon admin entry not found" });
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5679
|
+
res.type("application/javascript");
|
|
5680
|
+
res.sendFile(entryPath);
|
|
5681
|
+
}
|
|
5682
|
+
async function runAddonAction(req, res) {
|
|
5683
|
+
const parsed = z9.object({
|
|
5684
|
+
action: z9.string().min(1),
|
|
5685
|
+
input: z9.unknown().optional()
|
|
5686
|
+
}).safeParse(req.body);
|
|
5687
|
+
if (!parsed.success) {
|
|
5688
|
+
res.status(400).json({ error: "Body must include a valid action name" });
|
|
5689
|
+
return;
|
|
5690
|
+
}
|
|
5691
|
+
try {
|
|
5692
|
+
const result = await runAddonServerAction(req.params.id, parsed.data.action, parsed.data.input);
|
|
5693
|
+
res.json({ result });
|
|
5694
|
+
} catch (error) {
|
|
5695
|
+
const message = error instanceof Error ? error.message : "Could not run add-on action";
|
|
5696
|
+
const status = message === "Addon not found" ? 404 : message === "Addon is not installed" ? 409 : message === "Addon is disabled" || message === "Addon is not compatible with this Plank version" ? 409 : 400;
|
|
5697
|
+
res.status(status).json({ error: message });
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5700
|
+
async function updateAddonSettings(req, res) {
|
|
5701
|
+
const result = await getSettingsAddon(req.params.id);
|
|
5702
|
+
if ("error" in result) {
|
|
5703
|
+
res.status(result.error === "Addon not found" ? 404 : 400).json({ error: result.error });
|
|
5704
|
+
return;
|
|
5705
|
+
}
|
|
5706
|
+
const parsed = z9.record(z9.string(), z9.string()).safeParse(req.body);
|
|
5707
|
+
if (!parsed.success) {
|
|
5708
|
+
res.status(400).json({ error: "Body must be a flat key-value object" });
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
await setSettings(result.addon.settings_namespace, parsed.data);
|
|
5712
|
+
const settings = await getSettings(result.addon.settings_namespace);
|
|
5713
|
+
res.json(settings);
|
|
5714
|
+
}
|
|
5715
|
+
|
|
5006
5716
|
// ../core/dist/routes/admin.js
|
|
5007
5717
|
var router2 = Router2();
|
|
5008
5718
|
router2.use(authenticate);
|
|
@@ -5061,6 +5771,15 @@ router2.get("/editorial-mode", getEditorialMode);
|
|
|
5061
5771
|
router2.get("/version", getVersionInfo);
|
|
5062
5772
|
router2.get("/settings/:namespace", authorize("settings:overview:read"), getNamespaceSettings);
|
|
5063
5773
|
router2.put("/settings/:namespace", authorize("settings:overview:write"), updateNamespaceSettings);
|
|
5774
|
+
router2.get("/addons/registry", authorize("addons:read"), getAddonsRegistry);
|
|
5775
|
+
router2.get("/addons", authorize("addons:read"), listAddons);
|
|
5776
|
+
router2.post("/addons/:id/enable", authorize("addons:write"), enableAddon);
|
|
5777
|
+
router2.post("/addons/:id/disable", authorize("addons:write"), disableAddon);
|
|
5778
|
+
router2.get("/addons/:id/admin-module", authorize("addons:read"), getAddonAdminModuleDefinition);
|
|
5779
|
+
router2.get("/addons/:id/admin-entry.js", authorize("addons:read"), getAddonAdminEntry);
|
|
5780
|
+
router2.post("/addons/:id/actions", authorize("addons:read"), runAddonAction);
|
|
5781
|
+
router2.get("/addons/:id/settings", authorize("addons:read"), getAddonSettings);
|
|
5782
|
+
router2.put("/addons/:id/settings", authorize("addons:write"), updateAddonSettings);
|
|
5064
5783
|
router2.get("/webhooks", authorize("settings:webhooks:read"), listWebhooks);
|
|
5065
5784
|
router2.post("/webhooks", authorize("settings:webhooks:write"), createWebhook);
|
|
5066
5785
|
router2.delete("/webhooks/:id", authorize("settings:webhooks:delete"), deleteWebhook);
|
|
@@ -5772,9 +6491,9 @@ if (isDev) {
|
|
|
5772
6491
|
app.get("/admin/*path", (_req, res) => res.redirect(adminDevUrl));
|
|
5773
6492
|
app.get("/admin", (_req, res) => res.redirect(adminDevUrl));
|
|
5774
6493
|
} else {
|
|
5775
|
-
const adminDist = process.env.PLANK_ADMIN_DIST ??
|
|
6494
|
+
const adminDist = process.env.PLANK_ADMIN_DIST ?? join6(dirname3(fileURLToPath4(import.meta.url)), "../public/admin");
|
|
5776
6495
|
app.use("/admin", express.static(adminDist));
|
|
5777
|
-
app.get("/admin/*path", (_req, res) => res.sendFile(
|
|
6496
|
+
app.get("/admin/*path", (_req, res) => res.sendFile(join6(adminDist, "index.html")));
|
|
5778
6497
|
}
|
|
5779
6498
|
app.use(errorHandler);
|
|
5780
6499
|
var app_default = app;
|
|
@@ -5785,6 +6504,7 @@ async function start() {
|
|
|
5785
6504
|
const isDev2 = !process.env.PLANK_ADMIN_DIST;
|
|
5786
6505
|
await migrate();
|
|
5787
6506
|
await syncAllTables();
|
|
6507
|
+
await syncInstalledAddons();
|
|
5788
6508
|
app_default.listen(PORT2, () => {
|
|
5789
6509
|
const coreBase = `http://localhost:${PORT2}`;
|
|
5790
6510
|
const adminUrl = isDev2 ? "http://localhost:3000" : `${coreBase}/admin`;
|