@nuasite/cms-sidecar 0.43.0-beta.4 → 0.43.0-beta.8
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/cli.js +404 -192
- package/dist/types/server.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/server.ts +24 -1
- package/dist/types/cli.js +0 -77
- package/dist/types/concurrency.js +0 -47
- package/dist/types/index.js +0 -4
- package/dist/types/media-from-env.js +0 -57
- package/dist/types/server.js +0 -598
- package/dist/types/types.js +0 -10
package/dist/cli.js
CHANGED
|
@@ -21791,6 +21791,7 @@ var import_parser = __toESM(require_lib(), 1);
|
|
|
21791
21791
|
var FIELD_TYPES = [
|
|
21792
21792
|
"text",
|
|
21793
21793
|
"textarea",
|
|
21794
|
+
"markdown",
|
|
21794
21795
|
"date",
|
|
21795
21796
|
"datetime",
|
|
21796
21797
|
"time",
|
|
@@ -21828,7 +21829,8 @@ var FIELD_HELPER_TYPES = new Set([
|
|
|
21828
21829
|
"time",
|
|
21829
21830
|
"year",
|
|
21830
21831
|
"month",
|
|
21831
|
-
"textarea"
|
|
21832
|
+
"textarea",
|
|
21833
|
+
"markdown"
|
|
21832
21834
|
]);
|
|
21833
21835
|
var VALID_HINT_KEYS = new Set([
|
|
21834
21836
|
"min",
|
|
@@ -21943,6 +21945,8 @@ function parseConfigSource(source, _sourcePath) {
|
|
|
21943
21945
|
const loaderOptions = loaderProperty?.type === "ObjectProperty" ? extractGlobLoaderOptions(loaderProperty.value, bindings) : {};
|
|
21944
21946
|
const loaderPattern = loaderOptions.pattern;
|
|
21945
21947
|
const loaderBase = loaderOptions.base;
|
|
21948
|
+
const cmsProperty = decl.properties.find((p) => p.type === "ObjectProperty" && propertyKeyName(p.key) === "cms");
|
|
21949
|
+
const layout = cmsProperty?.type === "ObjectProperty" ? parseCmsLayout(cmsProperty.value, bindings) : undefined;
|
|
21946
21950
|
const schemaProperty = decl.properties.find((p) => p.type === "ObjectProperty" && propertyKeyName(p.key) === "schema");
|
|
21947
21951
|
if (!schemaProperty || schemaProperty.type !== "ObjectProperty") {
|
|
21948
21952
|
if (!loaderPattern)
|
|
@@ -21951,7 +21955,8 @@ function parseConfigSource(source, _sourcePath) {
|
|
|
21951
21955
|
name: collectionName,
|
|
21952
21956
|
fields: [],
|
|
21953
21957
|
loaderPattern,
|
|
21954
|
-
loaderBase
|
|
21958
|
+
loaderBase,
|
|
21959
|
+
layout
|
|
21955
21960
|
});
|
|
21956
21961
|
continue;
|
|
21957
21962
|
}
|
|
@@ -21963,7 +21968,8 @@ function parseConfigSource(source, _sourcePath) {
|
|
|
21963
21968
|
name: collectionName,
|
|
21964
21969
|
fields: [],
|
|
21965
21970
|
loaderPattern,
|
|
21966
|
-
loaderBase
|
|
21971
|
+
loaderBase,
|
|
21972
|
+
layout
|
|
21967
21973
|
});
|
|
21968
21974
|
continue;
|
|
21969
21975
|
}
|
|
@@ -21971,13 +21977,69 @@ function parseConfigSource(source, _sourcePath) {
|
|
|
21971
21977
|
name: collectionName,
|
|
21972
21978
|
fields: parseSchemaFields(schemaObject, bindings),
|
|
21973
21979
|
loaderPattern,
|
|
21974
|
-
loaderBase
|
|
21980
|
+
loaderBase,
|
|
21981
|
+
layout
|
|
21975
21982
|
});
|
|
21976
21983
|
}
|
|
21977
21984
|
return result;
|
|
21978
21985
|
}
|
|
21986
|
+
function parseCmsLayout(node, bindings) {
|
|
21987
|
+
const resolved = resolveExpression(node, bindings);
|
|
21988
|
+
if (resolved.type !== "ObjectExpression")
|
|
21989
|
+
return;
|
|
21990
|
+
const layout = {};
|
|
21991
|
+
for (const prop of resolved.properties) {
|
|
21992
|
+
if (prop.type !== "ObjectProperty")
|
|
21993
|
+
continue;
|
|
21994
|
+
const key = propertyKeyName(prop.key);
|
|
21995
|
+
const value = resolveExpression(prop.value, bindings);
|
|
21996
|
+
if (key === "display") {
|
|
21997
|
+
if (value.type === "StringLiteral" && (value.value === "tabs" || value.value === "sections"))
|
|
21998
|
+
layout.display = value.value;
|
|
21999
|
+
} else if (key === "sidebar") {
|
|
22000
|
+
if (value.type === "ArrayExpression")
|
|
22001
|
+
layout.sidebar = stringArray(value);
|
|
22002
|
+
} else if (key === "sections") {
|
|
22003
|
+
if (value.type === "ArrayExpression") {
|
|
22004
|
+
const sections = value.elements.map((el) => el && el.type !== "SpreadElement" ? parseLayoutSection(resolveExpression(el, bindings)) : null).filter((s) => s !== null);
|
|
22005
|
+
if (sections.length > 0)
|
|
22006
|
+
layout.sections = sections;
|
|
22007
|
+
}
|
|
22008
|
+
}
|
|
22009
|
+
}
|
|
22010
|
+
return Object.keys(layout).length > 0 ? layout : undefined;
|
|
22011
|
+
}
|
|
22012
|
+
function parseLayoutSection(node) {
|
|
22013
|
+
if (node.type !== "ObjectExpression")
|
|
22014
|
+
return null;
|
|
22015
|
+
let title;
|
|
22016
|
+
let fields = [];
|
|
22017
|
+
let collapsed = false;
|
|
22018
|
+
for (const prop of node.properties) {
|
|
22019
|
+
if (prop.type !== "ObjectProperty")
|
|
22020
|
+
continue;
|
|
22021
|
+
const key = propertyKeyName(prop.key);
|
|
22022
|
+
if (key === "title" && prop.value.type === "StringLiteral")
|
|
22023
|
+
title = prop.value.value;
|
|
22024
|
+
else if (key === "fields" && prop.value.type === "ArrayExpression")
|
|
22025
|
+
fields = stringArray(prop.value);
|
|
22026
|
+
else if (key === "collapsed" && prop.value.type === "BooleanLiteral")
|
|
22027
|
+
collapsed = prop.value.value;
|
|
22028
|
+
}
|
|
22029
|
+
if (title === undefined || fields.length === 0)
|
|
22030
|
+
return null;
|
|
22031
|
+
return collapsed ? { title, fields, collapsed } : { title, fields };
|
|
22032
|
+
}
|
|
22033
|
+
function stringArray(node) {
|
|
22034
|
+
const out = [];
|
|
22035
|
+
for (const el of node.elements) {
|
|
22036
|
+
if (el?.type === "StringLiteral")
|
|
22037
|
+
out.push(el.value);
|
|
22038
|
+
}
|
|
22039
|
+
return out;
|
|
22040
|
+
}
|
|
21979
22041
|
function isDefineCollectionCallee(callee) {
|
|
21980
|
-
return callee.type === "Identifier" && callee.name === "defineCollection";
|
|
22042
|
+
return callee.type === "Identifier" && (callee.name === "defineCollection" || callee.name === "defineCmsCollection");
|
|
21981
22043
|
}
|
|
21982
22044
|
function propertyKeyName(key) {
|
|
21983
22045
|
if (key.type === "Identifier")
|
|
@@ -22127,6 +22189,9 @@ function analyzeBaseCall(node, field, bindings) {
|
|
|
22127
22189
|
const hints = parseHintsFromObject(firstArg);
|
|
22128
22190
|
if (hints)
|
|
22129
22191
|
field.hints = hints;
|
|
22192
|
+
const layout = parseFieldLayoutFromObject(firstArg);
|
|
22193
|
+
if (layout)
|
|
22194
|
+
field.layout = layout;
|
|
22130
22195
|
}
|
|
22131
22196
|
return;
|
|
22132
22197
|
}
|
|
@@ -22219,6 +22284,49 @@ function assignHint(hints, key, value) {
|
|
|
22219
22284
|
return;
|
|
22220
22285
|
}
|
|
22221
22286
|
}
|
|
22287
|
+
function parseFieldLayoutFromObject(obj) {
|
|
22288
|
+
const layout = {};
|
|
22289
|
+
for (const prop of obj.properties) {
|
|
22290
|
+
if (prop.type !== "ObjectProperty")
|
|
22291
|
+
continue;
|
|
22292
|
+
const key = propertyKeyName(prop.key);
|
|
22293
|
+
const value = prop.value;
|
|
22294
|
+
switch (key) {
|
|
22295
|
+
case "label":
|
|
22296
|
+
if (value.type === "StringLiteral")
|
|
22297
|
+
layout.label = value.value;
|
|
22298
|
+
break;
|
|
22299
|
+
case "help":
|
|
22300
|
+
if (value.type === "StringLiteral")
|
|
22301
|
+
layout.help = value.value;
|
|
22302
|
+
break;
|
|
22303
|
+
case "group":
|
|
22304
|
+
if (value.type === "StringLiteral")
|
|
22305
|
+
layout.group = value.value;
|
|
22306
|
+
break;
|
|
22307
|
+
case "width":
|
|
22308
|
+
if (value.type === "StringLiteral" && (value.value === "full" || value.value === "half"))
|
|
22309
|
+
layout.width = value.value;
|
|
22310
|
+
break;
|
|
22311
|
+
case "order":
|
|
22312
|
+
if (value.type === "NumericLiteral") {
|
|
22313
|
+
layout.order = value.value;
|
|
22314
|
+
} else if (value.type === "UnaryExpression" && value.operator === "-" && value.argument.type === "NumericLiteral") {
|
|
22315
|
+
layout.order = -value.argument.value;
|
|
22316
|
+
}
|
|
22317
|
+
break;
|
|
22318
|
+
case "sidebar":
|
|
22319
|
+
if (value.type === "BooleanLiteral")
|
|
22320
|
+
layout.sidebar = value.value;
|
|
22321
|
+
break;
|
|
22322
|
+
case "hidden":
|
|
22323
|
+
if (value.type === "BooleanLiteral")
|
|
22324
|
+
layout.hidden = value.value;
|
|
22325
|
+
break;
|
|
22326
|
+
}
|
|
22327
|
+
}
|
|
22328
|
+
return Object.keys(layout).length > 0 ? layout : undefined;
|
|
22329
|
+
}
|
|
22222
22330
|
|
|
22223
22331
|
// ../cms-core/src/shared.ts
|
|
22224
22332
|
function slugifyHref(text) {
|
|
@@ -22548,6 +22656,41 @@ async function buildCollectionDefinition(fs, basePath, sources, collectionName,
|
|
|
22548
22656
|
assignFieldMetadata(def.fields, allDirectives);
|
|
22549
22657
|
return def;
|
|
22550
22658
|
}
|
|
22659
|
+
async function buildDataCollectionDefinition(fs, basePath, sources, collectionName, contentDir) {
|
|
22660
|
+
if (sources.length === 0)
|
|
22661
|
+
return null;
|
|
22662
|
+
const sourceBasePath = getCollectionSourceBasePath(basePath, collectionName, contentDir);
|
|
22663
|
+
const fieldMap = new Map;
|
|
22664
|
+
const entryInfos = [];
|
|
22665
|
+
const ext = sources.some((s) => s.relPath.endsWith(".json")) ? "json" : sources.some((s) => s.relPath.endsWith(".yaml")) ? "yaml" : "yml";
|
|
22666
|
+
const fileContents = await Promise.all(sources.map((s) => fs.readFile(path.join(basePath, s.relPath)).catch(() => null)));
|
|
22667
|
+
for (let i = 0;i < sources.length; i++) {
|
|
22668
|
+
const source = sources[i];
|
|
22669
|
+
const raw = fileContents[i];
|
|
22670
|
+
if (raw === null)
|
|
22671
|
+
continue;
|
|
22672
|
+
let data = null;
|
|
22673
|
+
try {
|
|
22674
|
+
data = source.relPath.endsWith(".json") ? JSON.parse(raw) : import_yaml.parse(raw);
|
|
22675
|
+
} catch {
|
|
22676
|
+
continue;
|
|
22677
|
+
}
|
|
22678
|
+
if (!data || typeof data !== "object")
|
|
22679
|
+
continue;
|
|
22680
|
+
const title = typeof data.name === "string" ? data.name : typeof data.title === "string" ? data.title : undefined;
|
|
22681
|
+
entryInfos.push({
|
|
22682
|
+
slug: source.slug,
|
|
22683
|
+
title,
|
|
22684
|
+
sourcePath: path.join(sourceBasePath, source.relPath),
|
|
22685
|
+
data
|
|
22686
|
+
});
|
|
22687
|
+
collectFieldObservations(fieldMap, data, sources.length);
|
|
22688
|
+
}
|
|
22689
|
+
return assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
|
|
22690
|
+
type: "data",
|
|
22691
|
+
fileExtension: ext
|
|
22692
|
+
});
|
|
22693
|
+
}
|
|
22551
22694
|
async function scanCollection(fs, collectionPath, collectionName, contentDir) {
|
|
22552
22695
|
const dirEntries = await fs.list(collectionPath);
|
|
22553
22696
|
if (dirEntries.length === 0)
|
|
@@ -22585,10 +22728,16 @@ async function scanCollection(fs, collectionPath, collectionName, contentDir) {
|
|
|
22585
22728
|
}
|
|
22586
22729
|
async function scanGlobCollection(fs, collectionName, baseRel, pattern, contentDir) {
|
|
22587
22730
|
const matches = await fs.glob(path.join(baseRel, pattern));
|
|
22588
|
-
const
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22731
|
+
const rels = matches.map((rel) => path.relative(baseRel, rel)).filter((relToBase) => !relToBase.split("/").some((seg) => seg.startsWith("_") || seg.startsWith(".")));
|
|
22732
|
+
const mdSources = rels.filter((relToBase) => relToBase.endsWith(".md") || relToBase.endsWith(".mdx")).map((relToBase) => ({ slug: relToBase.replace(/\.(md|mdx)$/, ""), relPath: relToBase }));
|
|
22733
|
+
if (mdSources.length > 0) {
|
|
22734
|
+
return await buildCollectionDefinition(fs, baseRel, mdSources, collectionName, contentDir);
|
|
22735
|
+
}
|
|
22736
|
+
const dataSources = rels.filter((relToBase) => /\.(ya?ml|json)$/.test(relToBase)).map((relToBase) => ({ slug: relToBase.replace(/\.(ya?ml|json)$/, ""), relPath: relToBase }));
|
|
22737
|
+
if (dataSources.length > 0) {
|
|
22738
|
+
return await buildDataCollectionDefinition(fs, baseRel, dataSources, collectionName, contentDir);
|
|
22739
|
+
}
|
|
22740
|
+
return null;
|
|
22592
22741
|
}
|
|
22593
22742
|
function applyParsedConfig(collections, parsed) {
|
|
22594
22743
|
for (const [collectionName, parsedColl] of parsed) {
|
|
@@ -22606,8 +22755,29 @@ function applyParsedConfig(collections, parsed) {
|
|
|
22606
22755
|
continue;
|
|
22607
22756
|
applyParsedFieldOverrides(field, pf);
|
|
22608
22757
|
}
|
|
22758
|
+
if (parsedColl.layout)
|
|
22759
|
+
def.layout = parsedColl.layout;
|
|
22609
22760
|
}
|
|
22610
22761
|
}
|
|
22762
|
+
function applyParsedFieldLayout(field, pf) {
|
|
22763
|
+
const layout = pf.layout;
|
|
22764
|
+
if (!layout)
|
|
22765
|
+
return;
|
|
22766
|
+
if (layout.label !== undefined)
|
|
22767
|
+
field.label = layout.label;
|
|
22768
|
+
if (layout.help !== undefined)
|
|
22769
|
+
field.help = layout.help;
|
|
22770
|
+
if (layout.group !== undefined)
|
|
22771
|
+
field.group = layout.group;
|
|
22772
|
+
if (layout.width !== undefined)
|
|
22773
|
+
field.width = layout.width;
|
|
22774
|
+
if (layout.order !== undefined)
|
|
22775
|
+
field.order = layout.order;
|
|
22776
|
+
if (layout.sidebar)
|
|
22777
|
+
field.position = "sidebar";
|
|
22778
|
+
if (layout.hidden)
|
|
22779
|
+
field.hidden = true;
|
|
22780
|
+
}
|
|
22611
22781
|
function applyParsedFieldOverrides(field, pf) {
|
|
22612
22782
|
if (pf.type) {
|
|
22613
22783
|
field.type = pf.type;
|
|
@@ -22621,6 +22791,7 @@ function applyParsedFieldOverrides(field, pf) {
|
|
|
22621
22791
|
if (pf.astroImage)
|
|
22622
22792
|
field.astroImage = true;
|
|
22623
22793
|
field.required = pf.required;
|
|
22794
|
+
applyParsedFieldLayout(field, pf);
|
|
22624
22795
|
if (pf.fields) {
|
|
22625
22796
|
const existingByName = new Map((field.fields ?? []).map((f) => [f.name, f]));
|
|
22626
22797
|
field.fields = pf.fields.map((subPf) => {
|
|
@@ -22649,6 +22820,7 @@ function parsedFieldToFieldDefinition(pf) {
|
|
|
22649
22820
|
fd.astroImage = true;
|
|
22650
22821
|
if (pf.fields)
|
|
22651
22822
|
fd.fields = pf.fields.map(parsedFieldToFieldDefinition);
|
|
22823
|
+
applyParsedFieldLayout(fd, pf);
|
|
22652
22824
|
return fd;
|
|
22653
22825
|
}
|
|
22654
22826
|
function applyCollectionOrderBy(collections, parsed) {
|
|
@@ -22874,38 +23046,7 @@ async function scanDataCollection(fs, collectionPath, collectionName, contentDir
|
|
|
22874
23046
|
if (entry)
|
|
22875
23047
|
sources.push(entry);
|
|
22876
23048
|
}
|
|
22877
|
-
|
|
22878
|
-
return null;
|
|
22879
|
-
const fieldMap = new Map;
|
|
22880
|
-
const entryInfos = [];
|
|
22881
|
-
const ext = sources.some((s) => s.relPath.endsWith(".json")) ? "json" : sources.some((s) => s.relPath.endsWith(".yaml")) ? "yaml" : "yml";
|
|
22882
|
-
const fileContents = await Promise.all(sources.map((s) => fs.readFile(path.join(collectionPath, s.relPath)).catch(() => null)));
|
|
22883
|
-
for (let i = 0;i < sources.length; i++) {
|
|
22884
|
-
const source = sources[i];
|
|
22885
|
-
const raw = fileContents[i];
|
|
22886
|
-
if (raw === null)
|
|
22887
|
-
continue;
|
|
22888
|
-
let data = null;
|
|
22889
|
-
try {
|
|
22890
|
-
data = source.relPath.endsWith(".json") ? JSON.parse(raw) : import_yaml.parse(raw);
|
|
22891
|
-
} catch {
|
|
22892
|
-
continue;
|
|
22893
|
-
}
|
|
22894
|
-
if (!data || typeof data !== "object")
|
|
22895
|
-
continue;
|
|
22896
|
-
const title = typeof data.name === "string" ? data.name : typeof data.title === "string" ? data.title : undefined;
|
|
22897
|
-
entryInfos.push({
|
|
22898
|
-
slug: source.slug,
|
|
22899
|
-
title,
|
|
22900
|
-
sourcePath: path.join(contentDir, collectionName, source.relPath),
|
|
22901
|
-
data
|
|
22902
|
-
});
|
|
22903
|
-
collectFieldObservations(fieldMap, data, sources.length);
|
|
22904
|
-
}
|
|
22905
|
-
return assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
|
|
22906
|
-
type: "data",
|
|
22907
|
-
fileExtension: ext
|
|
22908
|
-
});
|
|
23049
|
+
return await buildDataCollectionDefinition(fs, collectionPath, sources, collectionName, contentDir);
|
|
22909
23050
|
}
|
|
22910
23051
|
async function scanCollections(fs, contentDir = "src/content", parseCache = new Map) {
|
|
22911
23052
|
const collections = {};
|
|
@@ -23190,6 +23331,123 @@ function extractPreviewWidth(content) {
|
|
|
23190
23331
|
}
|
|
23191
23332
|
// ../cms-core/src/handlers/entry-ops.ts
|
|
23192
23333
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
23334
|
+
|
|
23335
|
+
// ../cms-core/src/media/local.ts
|
|
23336
|
+
import { randomUUID } from "crypto";
|
|
23337
|
+
import fs from "fs/promises";
|
|
23338
|
+
import path3 from "path";
|
|
23339
|
+
function createLocalStorageAdapter(options = {}) {
|
|
23340
|
+
const dir = path3.resolve(options.dir ?? "public/uploads");
|
|
23341
|
+
const urlPrefix = (options.urlPrefix ?? "/uploads").replace(/\/$/, "");
|
|
23342
|
+
return {
|
|
23343
|
+
staticFiles: { urlPrefix, dir },
|
|
23344
|
+
async list(opts) {
|
|
23345
|
+
const limit = opts?.limit ?? 50;
|
|
23346
|
+
const offset = opts?.cursor ? parseInt(opts.cursor, 10) : 0;
|
|
23347
|
+
const folder = opts?.folder ?? "";
|
|
23348
|
+
const targetDir = folder ? path3.join(dir, folder) : dir;
|
|
23349
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
23350
|
+
const entries = await fs.readdir(targetDir, { withFileTypes: true });
|
|
23351
|
+
const folders = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
|
|
23352
|
+
name: e.name,
|
|
23353
|
+
path: folder ? `${folder}/${e.name}` : e.name
|
|
23354
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
23355
|
+
const files = entries.filter((e) => e.isFile() && !e.name.startsWith("."));
|
|
23356
|
+
const withStats = await Promise.all(files.map(async (f) => {
|
|
23357
|
+
const filePath = path3.join(targetDir, f.name);
|
|
23358
|
+
const stat = await fs.stat(filePath);
|
|
23359
|
+
return { name: f.name, stat };
|
|
23360
|
+
}));
|
|
23361
|
+
withStats.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
23362
|
+
const slice = withStats.slice(offset, offset + limit);
|
|
23363
|
+
const hasMore = offset + limit < withStats.length;
|
|
23364
|
+
const urlFolder = folder ? `/${folder}` : "";
|
|
23365
|
+
const items = slice.map((f) => {
|
|
23366
|
+
const ext = path3.extname(f.name).toLowerCase();
|
|
23367
|
+
const contentType = mimeFromExt(ext);
|
|
23368
|
+
return {
|
|
23369
|
+
id: folder ? `${folder}/${f.name}` : f.name,
|
|
23370
|
+
url: `${urlPrefix}${urlFolder}/${f.name}`,
|
|
23371
|
+
filename: f.name,
|
|
23372
|
+
contentType,
|
|
23373
|
+
uploadedAt: f.stat.mtime.toISOString(),
|
|
23374
|
+
folder: folder || undefined
|
|
23375
|
+
};
|
|
23376
|
+
});
|
|
23377
|
+
return {
|
|
23378
|
+
items,
|
|
23379
|
+
folders,
|
|
23380
|
+
hasMore,
|
|
23381
|
+
cursor: hasMore ? String(offset + limit) : undefined
|
|
23382
|
+
};
|
|
23383
|
+
},
|
|
23384
|
+
async upload(file, filename, contentType, uploadOpts) {
|
|
23385
|
+
const folder = uploadOpts?.folder ?? "";
|
|
23386
|
+
const targetDir = folder ? path3.join(dir, folder) : dir;
|
|
23387
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
23388
|
+
const ext = getFileExtension(filename);
|
|
23389
|
+
const uuid = randomUUID();
|
|
23390
|
+
const newFilename = `${uuid}${ext ? `.${ext}` : ""}`;
|
|
23391
|
+
const filePath = path3.join(targetDir, newFilename);
|
|
23392
|
+
await fs.writeFile(filePath, file);
|
|
23393
|
+
const urlFolder = folder ? `/${folder}` : "";
|
|
23394
|
+
const id = folder ? `${folder}/${newFilename}` : newFilename;
|
|
23395
|
+
return {
|
|
23396
|
+
success: true,
|
|
23397
|
+
url: `${urlPrefix}${urlFolder}/${newFilename}`,
|
|
23398
|
+
filename: newFilename,
|
|
23399
|
+
id
|
|
23400
|
+
};
|
|
23401
|
+
},
|
|
23402
|
+
async delete(id) {
|
|
23403
|
+
const safePath = id.split("/").map((s) => path3.basename(s)).join("/");
|
|
23404
|
+
const filePath = path3.join(dir, safePath);
|
|
23405
|
+
try {
|
|
23406
|
+
await fs.unlink(filePath);
|
|
23407
|
+
return { success: true };
|
|
23408
|
+
} catch (error) {
|
|
23409
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23410
|
+
return { success: false, error: message };
|
|
23411
|
+
}
|
|
23412
|
+
},
|
|
23413
|
+
async createFolder(folder) {
|
|
23414
|
+
const segments = folder.split("/").filter(Boolean);
|
|
23415
|
+
if (segments.some((s) => s === ".." || s === ".")) {
|
|
23416
|
+
return { success: false, error: "Invalid folder name" };
|
|
23417
|
+
}
|
|
23418
|
+
try {
|
|
23419
|
+
await fs.mkdir(path3.join(dir, ...segments), { recursive: true });
|
|
23420
|
+
return { success: true };
|
|
23421
|
+
} catch (error) {
|
|
23422
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23423
|
+
return { success: false, error: message };
|
|
23424
|
+
}
|
|
23425
|
+
}
|
|
23426
|
+
};
|
|
23427
|
+
}
|
|
23428
|
+
function getFileExtension(filename) {
|
|
23429
|
+
const parts = filename.split(".");
|
|
23430
|
+
const ext = parts.length > 1 ? parts.pop()?.toLowerCase() ?? "" : "";
|
|
23431
|
+
return /^[a-z0-9]+$/.test(ext) ? ext : "";
|
|
23432
|
+
}
|
|
23433
|
+
var MIME_BY_EXT = {
|
|
23434
|
+
".jpg": "image/jpeg",
|
|
23435
|
+
".jpeg": "image/jpeg",
|
|
23436
|
+
".png": "image/png",
|
|
23437
|
+
".gif": "image/gif",
|
|
23438
|
+
".webp": "image/webp",
|
|
23439
|
+
".avif": "image/avif",
|
|
23440
|
+
".svg": "image/svg+xml",
|
|
23441
|
+
".ico": "image/x-icon",
|
|
23442
|
+
".mp4": "video/mp4",
|
|
23443
|
+
".webm": "video/webm",
|
|
23444
|
+
".pdf": "application/pdf"
|
|
23445
|
+
};
|
|
23446
|
+
function mimeFromExt(ext) {
|
|
23447
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
23448
|
+
}
|
|
23449
|
+
|
|
23450
|
+
// ../cms-core/src/handlers/entry-ops.ts
|
|
23193
23451
|
var MARKDOWN_EXTENSIONS = ["md", "mdx"];
|
|
23194
23452
|
function fileExtension(filePath) {
|
|
23195
23453
|
const idx = filePath.lastIndexOf(".");
|
|
@@ -23199,8 +23457,18 @@ function isDataFile(filePath) {
|
|
|
23199
23457
|
const ext = fileExtension(filePath);
|
|
23200
23458
|
return ext === "json" || ext === "yaml" || ext === "yml";
|
|
23201
23459
|
}
|
|
23460
|
+
async function resolveCollectionDir(deps, collection) {
|
|
23461
|
+
const parsed = await parseContentConfig(deps.fs, deps.parseCache);
|
|
23462
|
+
const loaderBase = parsed.get(collection)?.loaderBase;
|
|
23463
|
+
if (loaderBase) {
|
|
23464
|
+
const normalized = loaderBase.replace(/^\.\/+/, "").replace(/[/\\]+$/, "");
|
|
23465
|
+
if (normalized)
|
|
23466
|
+
return normalized;
|
|
23467
|
+
}
|
|
23468
|
+
return `${deps.contentDir}/${collection}`;
|
|
23469
|
+
}
|
|
23202
23470
|
async function resolveEntryPath(deps, collection, slug) {
|
|
23203
|
-
const base =
|
|
23471
|
+
const base = await resolveCollectionDir(deps, collection);
|
|
23204
23472
|
const flatExts = ["md", "mdx", "json", "yaml", "yml"];
|
|
23205
23473
|
for (const ext of flatExts) {
|
|
23206
23474
|
const candidate = `${base}/${slug}.${ext}`;
|
|
@@ -23214,6 +23482,36 @@ async function resolveEntryPath(deps, collection, slug) {
|
|
|
23214
23482
|
}
|
|
23215
23483
|
return null;
|
|
23216
23484
|
}
|
|
23485
|
+
function resolveRelativePath(baseDir, rel) {
|
|
23486
|
+
const out = rel.startsWith("/") ? [] : baseDir.split("/").filter(Boolean);
|
|
23487
|
+
for (const seg of rel.replace(/^\/+/, "").split("/")) {
|
|
23488
|
+
if (seg === "" || seg === ".")
|
|
23489
|
+
continue;
|
|
23490
|
+
if (seg === "..") {
|
|
23491
|
+
if (out.length === 0)
|
|
23492
|
+
return null;
|
|
23493
|
+
out.pop();
|
|
23494
|
+
continue;
|
|
23495
|
+
}
|
|
23496
|
+
out.push(seg);
|
|
23497
|
+
}
|
|
23498
|
+
return out.join("/");
|
|
23499
|
+
}
|
|
23500
|
+
function extOf(filePath) {
|
|
23501
|
+
const idx = filePath.lastIndexOf(".");
|
|
23502
|
+
return idx >= 0 ? filePath.slice(idx).toLowerCase() : "";
|
|
23503
|
+
}
|
|
23504
|
+
async function getEntryAsset(deps, collection, slug, assetPath) {
|
|
23505
|
+
const sourcePath = await resolveEntryPath(deps, collection, slug);
|
|
23506
|
+
if (!sourcePath)
|
|
23507
|
+
return null;
|
|
23508
|
+
const lastSlash = sourcePath.lastIndexOf("/");
|
|
23509
|
+
const baseDir = lastSlash >= 0 ? sourcePath.slice(0, lastSlash) : "";
|
|
23510
|
+
const resolved = resolveRelativePath(baseDir, assetPath);
|
|
23511
|
+
if (!resolved || !await deps.fs.exists(resolved))
|
|
23512
|
+
return null;
|
|
23513
|
+
return { bytes: await deps.fs.readBytes(resolved), contentType: mimeFromExt(extOf(resolved)) };
|
|
23514
|
+
}
|
|
23217
23515
|
function parseFrontmatter2(raw) {
|
|
23218
23516
|
const trimmed = raw.trimStart();
|
|
23219
23517
|
if (!trimmed.startsWith("---")) {
|
|
@@ -23331,7 +23629,7 @@ async function detectCollectionMarkdownLayout(deps, collection) {
|
|
|
23331
23629
|
return "flat";
|
|
23332
23630
|
}
|
|
23333
23631
|
async function inferLayoutFromExistingEntries(deps, collection) {
|
|
23334
|
-
const collectionPath =
|
|
23632
|
+
const collectionPath = await resolveCollectionDir(deps, collection);
|
|
23335
23633
|
const dirEntries = await deps.fs.list(collectionPath);
|
|
23336
23634
|
if (dirEntries.length === 0)
|
|
23337
23635
|
return null;
|
|
@@ -23405,7 +23703,8 @@ async function createEntry(deps, input) {
|
|
|
23405
23703
|
}
|
|
23406
23704
|
const isData = ext === "json" || ext === "yaml" || ext === "yml";
|
|
23407
23705
|
const layout = isData ? "flat" : await detectCollectionMarkdownLayout(deps, collection);
|
|
23408
|
-
const
|
|
23706
|
+
const collectionDir = await resolveCollectionDir(deps, collection);
|
|
23707
|
+
const sourcePath = layout === "index" ? `${collectionDir}/${normalizedSlug}/index.${ext}` : `${collectionDir}/${normalizedSlug}.${ext}`;
|
|
23409
23708
|
let fileContent;
|
|
23410
23709
|
if (isData) {
|
|
23411
23710
|
fileContent = ext === "json" ? JSON.stringify({ ...frontmatter }, null, 2) + `
|
|
@@ -23816,27 +24115,30 @@ function parseRedirectLines(lines) {
|
|
|
23816
24115
|
}
|
|
23817
24116
|
|
|
23818
24117
|
// ../cms-core/src/core.ts
|
|
23819
|
-
function createCmsCore(
|
|
24118
|
+
function createCmsCore(fs2, opts = {}) {
|
|
23820
24119
|
const contentDir = opts.contentDir ?? "src/content";
|
|
23821
24120
|
const componentDirs = opts.componentDirs ?? ["src/components"];
|
|
23822
24121
|
const parseCache = new Map;
|
|
23823
24122
|
const entryDeps = {
|
|
23824
|
-
fs,
|
|
24123
|
+
fs: fs2,
|
|
23825
24124
|
contentDir,
|
|
23826
24125
|
parseCache,
|
|
23827
24126
|
componentDirs,
|
|
23828
|
-
resolveComponentDefinitions: () => scanComponentDefinitions(
|
|
24127
|
+
resolveComponentDefinitions: () => scanComponentDefinitions(fs2, componentDirs)
|
|
23829
24128
|
};
|
|
23830
24129
|
return {
|
|
23831
24130
|
scanCollections() {
|
|
23832
|
-
return scanCollections(
|
|
24131
|
+
return scanCollections(fs2, contentDir, parseCache);
|
|
23833
24132
|
},
|
|
23834
24133
|
scanComponents() {
|
|
23835
|
-
return scanComponentDefinitions(
|
|
24134
|
+
return scanComponentDefinitions(fs2, componentDirs);
|
|
23836
24135
|
},
|
|
23837
24136
|
getEntry(collection, slug) {
|
|
23838
24137
|
return getEntry(entryDeps, collection, slug);
|
|
23839
24138
|
},
|
|
24139
|
+
getEntryAsset(collection, slug, assetPath) {
|
|
24140
|
+
return getEntryAsset(entryDeps, collection, slug, assetPath);
|
|
24141
|
+
},
|
|
23840
24142
|
createEntry(input) {
|
|
23841
24143
|
return createEntry(entryDeps, input);
|
|
23842
24144
|
},
|
|
@@ -23856,28 +24158,28 @@ function createCmsCore(fs, opts = {}) {
|
|
|
23856
24158
|
return removeArrayItem(entryDeps, input);
|
|
23857
24159
|
},
|
|
23858
24160
|
createPage(input) {
|
|
23859
|
-
return createPage({ fs }, input);
|
|
24161
|
+
return createPage({ fs: fs2 }, input);
|
|
23860
24162
|
},
|
|
23861
24163
|
duplicatePage(input) {
|
|
23862
|
-
return duplicatePage({ fs }, input);
|
|
24164
|
+
return duplicatePage({ fs: fs2 }, input);
|
|
23863
24165
|
},
|
|
23864
24166
|
deletePage(input) {
|
|
23865
|
-
return deletePage({ fs }, input);
|
|
24167
|
+
return deletePage({ fs: fs2 }, input);
|
|
23866
24168
|
},
|
|
23867
24169
|
getLayouts() {
|
|
23868
|
-
return getLayouts({ fs });
|
|
24170
|
+
return getLayouts({ fs: fs2 });
|
|
23869
24171
|
},
|
|
23870
24172
|
listRedirects() {
|
|
23871
|
-
return listRedirects({ fs });
|
|
24173
|
+
return listRedirects({ fs: fs2 });
|
|
23872
24174
|
},
|
|
23873
24175
|
addRedirect(input) {
|
|
23874
|
-
return addRedirect({ fs }, input);
|
|
24176
|
+
return addRedirect({ fs: fs2 }, input);
|
|
23875
24177
|
},
|
|
23876
24178
|
updateRedirect(input) {
|
|
23877
|
-
return updateRedirect({ fs }, input);
|
|
24179
|
+
return updateRedirect({ fs: fs2 }, input);
|
|
23878
24180
|
},
|
|
23879
24181
|
deleteRedirect(input) {
|
|
23880
|
-
return deleteRedirect({ fs }, input);
|
|
24182
|
+
return deleteRedirect({ fs: fs2 }, input);
|
|
23881
24183
|
},
|
|
23882
24184
|
media: opts.media
|
|
23883
24185
|
};
|
|
@@ -23916,14 +24218,14 @@ function globToRegExp(glob) {
|
|
|
23916
24218
|
return new RegExp(`^${re}$`);
|
|
23917
24219
|
}
|
|
23918
24220
|
// ../cms-core/src/fs/node-fs.ts
|
|
23919
|
-
import
|
|
23920
|
-
import
|
|
24221
|
+
import fs2 from "fs/promises";
|
|
24222
|
+
import path4 from "path";
|
|
23921
24223
|
function resolveWithinRoot(root, filePath) {
|
|
23922
|
-
const resolvedRoot =
|
|
24224
|
+
const resolvedRoot = path4.resolve(root);
|
|
23923
24225
|
const isAbsoluteFs = filePath.startsWith(resolvedRoot);
|
|
23924
24226
|
const normalizedPath = !isAbsoluteFs && filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
23925
|
-
const fullPath =
|
|
23926
|
-
if (!fullPath.startsWith(resolvedRoot +
|
|
24227
|
+
const fullPath = path4.isAbsolute(normalizedPath) ? path4.resolve(normalizedPath) : path4.resolve(resolvedRoot, normalizedPath);
|
|
24228
|
+
if (!fullPath.startsWith(resolvedRoot + path4.sep) && fullPath !== resolvedRoot) {
|
|
23927
24229
|
throw new Error(`Path traversal detected: ${filePath}`);
|
|
23928
24230
|
}
|
|
23929
24231
|
return fullPath;
|
|
@@ -23934,17 +24236,17 @@ function isNodeError(error) {
|
|
|
23934
24236
|
async function walkFiles(absRoot, absDir) {
|
|
23935
24237
|
let dirEntries;
|
|
23936
24238
|
try {
|
|
23937
|
-
dirEntries = await
|
|
24239
|
+
dirEntries = await fs2.readdir(absDir, { withFileTypes: true });
|
|
23938
24240
|
} catch {
|
|
23939
24241
|
return [];
|
|
23940
24242
|
}
|
|
23941
24243
|
const out = [];
|
|
23942
24244
|
for (const entry of dirEntries) {
|
|
23943
|
-
const abs =
|
|
24245
|
+
const abs = path4.join(absDir, entry.name);
|
|
23944
24246
|
if (entry.isDirectory()) {
|
|
23945
24247
|
out.push(...await walkFiles(absRoot, abs));
|
|
23946
24248
|
} else if (entry.isFile()) {
|
|
23947
|
-
out.push(
|
|
24249
|
+
out.push(path4.relative(absRoot, abs).split(path4.sep).join("/"));
|
|
23948
24250
|
}
|
|
23949
24251
|
}
|
|
23950
24252
|
return out;
|
|
@@ -23963,35 +24265,38 @@ function staticPrefixDir(pattern) {
|
|
|
23963
24265
|
return staticSegments.join("/");
|
|
23964
24266
|
}
|
|
23965
24267
|
function createNodeFs(root) {
|
|
23966
|
-
const resolvedRoot =
|
|
24268
|
+
const resolvedRoot = path4.resolve(root);
|
|
23967
24269
|
const resolve = (p) => resolveWithinRoot(resolvedRoot, p);
|
|
23968
24270
|
return {
|
|
23969
24271
|
async readFile(filePath) {
|
|
23970
|
-
return
|
|
24272
|
+
return fs2.readFile(resolve(filePath), "utf-8");
|
|
24273
|
+
},
|
|
24274
|
+
async readBytes(filePath) {
|
|
24275
|
+
return fs2.readFile(resolve(filePath));
|
|
23971
24276
|
},
|
|
23972
24277
|
async writeFile(filePath, content) {
|
|
23973
24278
|
const fullPath = resolve(filePath);
|
|
23974
|
-
await
|
|
24279
|
+
await fs2.mkdir(path4.dirname(fullPath), { recursive: true });
|
|
23975
24280
|
const tempPath = `${fullPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
23976
24281
|
try {
|
|
23977
|
-
await
|
|
23978
|
-
await
|
|
24282
|
+
await fs2.writeFile(tempPath, content, "utf-8");
|
|
24283
|
+
await fs2.rename(tempPath, fullPath);
|
|
23979
24284
|
} catch (error) {
|
|
23980
|
-
await
|
|
24285
|
+
await fs2.rm(tempPath, { force: true });
|
|
23981
24286
|
throw error;
|
|
23982
24287
|
}
|
|
23983
24288
|
},
|
|
23984
24289
|
async rename(from, to) {
|
|
23985
24290
|
const fullTo = resolve(to);
|
|
23986
|
-
await
|
|
23987
|
-
await
|
|
24291
|
+
await fs2.mkdir(path4.dirname(fullTo), { recursive: true });
|
|
24292
|
+
await fs2.rename(resolve(from), fullTo);
|
|
23988
24293
|
},
|
|
23989
24294
|
async remove(filePath) {
|
|
23990
|
-
await
|
|
24295
|
+
await fs2.rm(resolve(filePath), { force: true });
|
|
23991
24296
|
},
|
|
23992
24297
|
async exists(filePath) {
|
|
23993
24298
|
try {
|
|
23994
|
-
await
|
|
24299
|
+
await fs2.access(resolve(filePath));
|
|
23995
24300
|
return true;
|
|
23996
24301
|
} catch {
|
|
23997
24302
|
return false;
|
|
@@ -23999,7 +24304,7 @@ function createNodeFs(root) {
|
|
|
23999
24304
|
},
|
|
24000
24305
|
async list(dir) {
|
|
24001
24306
|
try {
|
|
24002
|
-
const entries = await
|
|
24307
|
+
const entries = await fs2.readdir(resolve(dir), { withFileTypes: true });
|
|
24003
24308
|
return entries.map((entry) => ({ name: entry.name, isDirectory: entry.isDirectory() }));
|
|
24004
24309
|
} catch (error) {
|
|
24005
24310
|
if (isNodeError(error) && error.code === "ENOENT")
|
|
@@ -24015,7 +24320,7 @@ function createNodeFs(root) {
|
|
|
24015
24320
|
return files.filter((rel) => matcher.test(rel));
|
|
24016
24321
|
},
|
|
24017
24322
|
async stat(filePath) {
|
|
24018
|
-
const s = await
|
|
24323
|
+
const s = await fs2.stat(resolve(filePath));
|
|
24019
24324
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
24020
24325
|
}
|
|
24021
24326
|
};
|
|
@@ -24083,120 +24388,6 @@ function createContemberStorageAdapter(options) {
|
|
|
24083
24388
|
}
|
|
24084
24389
|
};
|
|
24085
24390
|
}
|
|
24086
|
-
// ../cms-core/src/media/local.ts
|
|
24087
|
-
import { randomUUID } from "crypto";
|
|
24088
|
-
import fs2 from "fs/promises";
|
|
24089
|
-
import path4 from "path";
|
|
24090
|
-
function createLocalStorageAdapter(options = {}) {
|
|
24091
|
-
const dir = path4.resolve(options.dir ?? "public/uploads");
|
|
24092
|
-
const urlPrefix = (options.urlPrefix ?? "/uploads").replace(/\/$/, "");
|
|
24093
|
-
return {
|
|
24094
|
-
staticFiles: { urlPrefix, dir },
|
|
24095
|
-
async list(opts) {
|
|
24096
|
-
const limit = opts?.limit ?? 50;
|
|
24097
|
-
const offset = opts?.cursor ? parseInt(opts.cursor, 10) : 0;
|
|
24098
|
-
const folder = opts?.folder ?? "";
|
|
24099
|
-
const targetDir = folder ? path4.join(dir, folder) : dir;
|
|
24100
|
-
await fs2.mkdir(targetDir, { recursive: true });
|
|
24101
|
-
const entries = await fs2.readdir(targetDir, { withFileTypes: true });
|
|
24102
|
-
const folders = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
|
|
24103
|
-
name: e.name,
|
|
24104
|
-
path: folder ? `${folder}/${e.name}` : e.name
|
|
24105
|
-
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
24106
|
-
const files = entries.filter((e) => e.isFile() && !e.name.startsWith("."));
|
|
24107
|
-
const withStats = await Promise.all(files.map(async (f) => {
|
|
24108
|
-
const filePath = path4.join(targetDir, f.name);
|
|
24109
|
-
const stat = await fs2.stat(filePath);
|
|
24110
|
-
return { name: f.name, stat };
|
|
24111
|
-
}));
|
|
24112
|
-
withStats.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
|
|
24113
|
-
const slice = withStats.slice(offset, offset + limit);
|
|
24114
|
-
const hasMore = offset + limit < withStats.length;
|
|
24115
|
-
const urlFolder = folder ? `/${folder}` : "";
|
|
24116
|
-
const items = slice.map((f) => {
|
|
24117
|
-
const ext = path4.extname(f.name).toLowerCase();
|
|
24118
|
-
const contentType = mimeFromExt(ext);
|
|
24119
|
-
return {
|
|
24120
|
-
id: folder ? `${folder}/${f.name}` : f.name,
|
|
24121
|
-
url: `${urlPrefix}${urlFolder}/${f.name}`,
|
|
24122
|
-
filename: f.name,
|
|
24123
|
-
contentType,
|
|
24124
|
-
uploadedAt: f.stat.mtime.toISOString(),
|
|
24125
|
-
folder: folder || undefined
|
|
24126
|
-
};
|
|
24127
|
-
});
|
|
24128
|
-
return {
|
|
24129
|
-
items,
|
|
24130
|
-
folders,
|
|
24131
|
-
hasMore,
|
|
24132
|
-
cursor: hasMore ? String(offset + limit) : undefined
|
|
24133
|
-
};
|
|
24134
|
-
},
|
|
24135
|
-
async upload(file, filename, contentType, uploadOpts) {
|
|
24136
|
-
const folder = uploadOpts?.folder ?? "";
|
|
24137
|
-
const targetDir = folder ? path4.join(dir, folder) : dir;
|
|
24138
|
-
await fs2.mkdir(targetDir, { recursive: true });
|
|
24139
|
-
const ext = getFileExtension(filename);
|
|
24140
|
-
const uuid = randomUUID();
|
|
24141
|
-
const newFilename = `${uuid}${ext ? `.${ext}` : ""}`;
|
|
24142
|
-
const filePath = path4.join(targetDir, newFilename);
|
|
24143
|
-
await fs2.writeFile(filePath, file);
|
|
24144
|
-
const urlFolder = folder ? `/${folder}` : "";
|
|
24145
|
-
const id = folder ? `${folder}/${newFilename}` : newFilename;
|
|
24146
|
-
return {
|
|
24147
|
-
success: true,
|
|
24148
|
-
url: `${urlPrefix}${urlFolder}/${newFilename}`,
|
|
24149
|
-
filename: newFilename,
|
|
24150
|
-
id
|
|
24151
|
-
};
|
|
24152
|
-
},
|
|
24153
|
-
async delete(id) {
|
|
24154
|
-
const safePath = id.split("/").map((s) => path4.basename(s)).join("/");
|
|
24155
|
-
const filePath = path4.join(dir, safePath);
|
|
24156
|
-
try {
|
|
24157
|
-
await fs2.unlink(filePath);
|
|
24158
|
-
return { success: true };
|
|
24159
|
-
} catch (error) {
|
|
24160
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
24161
|
-
return { success: false, error: message };
|
|
24162
|
-
}
|
|
24163
|
-
},
|
|
24164
|
-
async createFolder(folder) {
|
|
24165
|
-
const segments = folder.split("/").filter(Boolean);
|
|
24166
|
-
if (segments.some((s) => s === ".." || s === ".")) {
|
|
24167
|
-
return { success: false, error: "Invalid folder name" };
|
|
24168
|
-
}
|
|
24169
|
-
try {
|
|
24170
|
-
await fs2.mkdir(path4.join(dir, ...segments), { recursive: true });
|
|
24171
|
-
return { success: true };
|
|
24172
|
-
} catch (error) {
|
|
24173
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
24174
|
-
return { success: false, error: message };
|
|
24175
|
-
}
|
|
24176
|
-
}
|
|
24177
|
-
};
|
|
24178
|
-
}
|
|
24179
|
-
function getFileExtension(filename) {
|
|
24180
|
-
const parts = filename.split(".");
|
|
24181
|
-
const ext = parts.length > 1 ? parts.pop()?.toLowerCase() ?? "" : "";
|
|
24182
|
-
return /^[a-z0-9]+$/.test(ext) ? ext : "";
|
|
24183
|
-
}
|
|
24184
|
-
var MIME_BY_EXT = {
|
|
24185
|
-
".jpg": "image/jpeg",
|
|
24186
|
-
".jpeg": "image/jpeg",
|
|
24187
|
-
".png": "image/png",
|
|
24188
|
-
".gif": "image/gif",
|
|
24189
|
-
".webp": "image/webp",
|
|
24190
|
-
".avif": "image/avif",
|
|
24191
|
-
".svg": "image/svg+xml",
|
|
24192
|
-
".ico": "image/x-icon",
|
|
24193
|
-
".mp4": "video/mp4",
|
|
24194
|
-
".webm": "video/webm",
|
|
24195
|
-
".pdf": "application/pdf"
|
|
24196
|
-
};
|
|
24197
|
-
function mimeFromExt(ext) {
|
|
24198
|
-
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
24199
|
-
}
|
|
24200
24391
|
// ../cms-core/src/media/project-images.ts
|
|
24201
24392
|
var IMAGE_EXTENSIONS2 = new Set(Object.entries(MIME_BY_EXT).filter(([, mime]) => mime.startsWith("image/")).map(([ext]) => ext));
|
|
24202
24393
|
// ../cms-core/src/media/s3.ts
|
|
@@ -24424,12 +24615,14 @@ var SIDECAR_FEATURES = [
|
|
|
24424
24615
|
"entry.crud",
|
|
24425
24616
|
"entry.rename",
|
|
24426
24617
|
"entry.array",
|
|
24618
|
+
"entry.asset",
|
|
24427
24619
|
"entry.optimistic-concurrency",
|
|
24428
24620
|
"pages.crud",
|
|
24429
24621
|
"pages.list",
|
|
24430
24622
|
"pages.layouts",
|
|
24431
24623
|
"redirects.crud",
|
|
24432
|
-
"media"
|
|
24624
|
+
"media",
|
|
24625
|
+
"components"
|
|
24433
24626
|
];
|
|
24434
24627
|
var API_PREFIX = "/cms/v1";
|
|
24435
24628
|
var DEFAULT_LIMIT = 50;
|
|
@@ -24591,6 +24784,10 @@ function createServer(opts) {
|
|
|
24591
24784
|
const map = await core.scanCollections();
|
|
24592
24785
|
return map[name] ?? null;
|
|
24593
24786
|
}
|
|
24787
|
+
async function scanComponentsList() {
|
|
24788
|
+
const map = await core.scanComponents();
|
|
24789
|
+
return Object.values(map).sort((a, b) => a.name.localeCompare(b.name));
|
|
24790
|
+
}
|
|
24594
24791
|
async function entryDetail(collection, slug) {
|
|
24595
24792
|
const result = await core.getEntry(collection, slug);
|
|
24596
24793
|
if (!result)
|
|
@@ -24610,6 +24807,15 @@ function createServer(opts) {
|
|
|
24610
24807
|
};
|
|
24611
24808
|
return json(entry);
|
|
24612
24809
|
}
|
|
24810
|
+
async function assetResponse(collection, slug, url) {
|
|
24811
|
+
const assetPath = url.searchParams.get("path");
|
|
24812
|
+
if (assetPath === null || assetPath === "")
|
|
24813
|
+
return error("validation", 'A "path" query parameter is required');
|
|
24814
|
+
const asset = await core.getEntryAsset(collection, slug, assetPath);
|
|
24815
|
+
if (!asset)
|
|
24816
|
+
return error("not_found", `Asset not found for ${collection}/${slug}: ${assetPath}`);
|
|
24817
|
+
return new Response(asset.bytes, { headers: { "content-type": asset.contentType, "cache-control": "no-cache" } });
|
|
24818
|
+
}
|
|
24613
24819
|
async function patchEntry(collection, slug, body) {
|
|
24614
24820
|
const existing = await core.getEntry(collection, slug);
|
|
24615
24821
|
if (!existing)
|
|
@@ -24674,6 +24880,10 @@ function createServer(opts) {
|
|
|
24674
24880
|
return json(model);
|
|
24675
24881
|
}
|
|
24676
24882
|
break;
|
|
24883
|
+
case "components":
|
|
24884
|
+
if (method === "GET")
|
|
24885
|
+
return json(await scanComponentsList());
|
|
24886
|
+
break;
|
|
24677
24887
|
case "collections":
|
|
24678
24888
|
return routeCollections(method, tail, req, url);
|
|
24679
24889
|
case "pages":
|
|
@@ -24715,6 +24925,8 @@ function createServer(opts) {
|
|
|
24715
24925
|
return addArrayRoute(collection, slug, req);
|
|
24716
24926
|
if (action === "array" && method === "DELETE")
|
|
24717
24927
|
return removeArrayRoute(collection, slug, req);
|
|
24928
|
+
if (action === "asset" && method === "GET")
|
|
24929
|
+
return assetResponse(collection, slug, url);
|
|
24718
24930
|
}
|
|
24719
24931
|
return error("not_found", `No route: ${method} /cms/v1/collections/${tail.join("/")}`);
|
|
24720
24932
|
}
|