@bobbykim/manguito-cms-cli 0.8.2 → 0.9.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.
|
@@ -25993,6 +25993,22 @@ function buildFields(rawFields, sourceFile, ownerTableName) {
|
|
|
25993
25993
|
}
|
|
25994
25994
|
return { fields, errors };
|
|
25995
25995
|
}
|
|
25996
|
+
function collectJunctionTables(fields) {
|
|
25997
|
+
const junctionTables = [];
|
|
25998
|
+
for (const field of fields) {
|
|
25999
|
+
if (field.field_type === "reference" && field.db_column?.junction) {
|
|
26000
|
+
const j = field.db_column.junction;
|
|
26001
|
+
junctionTables.push({
|
|
26002
|
+
table_name: j.table_name,
|
|
26003
|
+
left_column: j.left_column,
|
|
26004
|
+
right_column: j.right_column,
|
|
26005
|
+
right_table: j.right_table,
|
|
26006
|
+
order_column: j.order_column
|
|
26007
|
+
});
|
|
26008
|
+
}
|
|
26009
|
+
}
|
|
26010
|
+
return junctionTables;
|
|
26011
|
+
}
|
|
25996
26012
|
function parseContentType(raw, sourceFile) {
|
|
25997
26013
|
const result = ContentTypeRawSchema.safeParse(raw);
|
|
25998
26014
|
if (!result.success) {
|
|
@@ -26018,19 +26034,6 @@ function parseContentType(raw, sourceFile) {
|
|
|
26018
26034
|
const tableName = machineNameToTableName(v.name);
|
|
26019
26035
|
const { fields, errors } = buildFields(flatRawFields, sourceFile, tableName);
|
|
26020
26036
|
if (errors.length > 0) return { ok: false, errors };
|
|
26021
|
-
const junctionTables = [];
|
|
26022
|
-
for (const field of fields) {
|
|
26023
|
-
if (field.field_type === "reference" && field.db_column?.junction) {
|
|
26024
|
-
const j = field.db_column.junction;
|
|
26025
|
-
junctionTables.push({
|
|
26026
|
-
table_name: j.table_name,
|
|
26027
|
-
left_column: j.left_column,
|
|
26028
|
-
right_column: j.right_column,
|
|
26029
|
-
right_table: j.right_table,
|
|
26030
|
-
order_column: j.order_column
|
|
26031
|
-
});
|
|
26032
|
-
}
|
|
26033
|
-
}
|
|
26034
26037
|
const nameKebab = nameSegmentToKebab(getNameSegment(v.name));
|
|
26035
26038
|
const api = v.only_one ? {
|
|
26036
26039
|
default_base_path: v.default_base_path,
|
|
@@ -26052,7 +26055,7 @@ function parseContentType(raw, sourceFile) {
|
|
|
26052
26055
|
system_fields: CONTENT_SYSTEM_FIELDS,
|
|
26053
26056
|
fields,
|
|
26054
26057
|
ui: { tabs },
|
|
26055
|
-
db: { table_name: tableName, junction_tables:
|
|
26058
|
+
db: { table_name: tableName, junction_tables: collectJunctionTables(fields) },
|
|
26056
26059
|
api
|
|
26057
26060
|
};
|
|
26058
26061
|
return { ok: true, schema };
|
|
@@ -26105,7 +26108,7 @@ function parseTaxonomyType(raw, sourceFile) {
|
|
|
26105
26108
|
source_file: sourceFile,
|
|
26106
26109
|
system_fields: TAXONOMY_SYSTEM_FIELDS,
|
|
26107
26110
|
fields,
|
|
26108
|
-
db: { table_name: tableName },
|
|
26111
|
+
db: { table_name: tableName, junction_tables: collectJunctionTables(fields) },
|
|
26109
26112
|
api: {
|
|
26110
26113
|
collection_path: basePath,
|
|
26111
26114
|
item_path: `${basePath}/:id`
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isColumnBacked,
|
|
13
13
|
relationCardinality,
|
|
14
14
|
resolveRelationField
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-L36JYVXK.js";
|
|
16
16
|
|
|
17
17
|
// ../../node_modules/.pnpm/@whatwg-node+fetch@0.10.13/node_modules/@whatwg-node/fetch/dist/shouldSkipPonyfill.js
|
|
18
18
|
var require_shouldSkipPonyfill = __commonJS({
|
package/dist/index.js
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
validateCrossReferences,
|
|
55
55
|
verifyPassword,
|
|
56
56
|
walkSchemaDirectory
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-L36JYVXK.js";
|
|
58
58
|
|
|
59
59
|
// ../../node_modules/.pnpm/postgres-array@2.0.0/node_modules/postgres-array/index.js
|
|
60
60
|
var require_postgres_array = __commonJS({
|
|
@@ -16975,9 +16975,13 @@ function orderParagraphTypes(paragraphs) {
|
|
|
16975
16975
|
}
|
|
16976
16976
|
function generateJunctionTables(registry) {
|
|
16977
16977
|
const tables = [];
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
16978
|
+
const owners = [
|
|
16979
|
+
...Object.values(registry.content_types),
|
|
16980
|
+
...Object.values(registry.taxonomy_types)
|
|
16981
|
+
];
|
|
16982
|
+
for (const owner of owners) {
|
|
16983
|
+
const leftVar = tableNameToVarName(owner.db.table_name);
|
|
16984
|
+
for (const jt of owner.db.junction_tables) {
|
|
16981
16985
|
const varName = tableNameToVarName(jt.table_name);
|
|
16982
16986
|
const rightVar = tableNameToVarName(jt.right_table);
|
|
16983
16987
|
const orderLine = jt.order_column ? ` order: s.integer('order').notNull().default(0),` : null;
|
|
@@ -22939,6 +22943,46 @@ async function loadParagraphRows(db, registry, pType, parentId, fieldName) {
|
|
|
22939
22943
|
}
|
|
22940
22944
|
return rows;
|
|
22941
22945
|
}
|
|
22946
|
+
async function loadRelationsForEdit(db, registry, fields, id, row) {
|
|
22947
|
+
for (const f of fields) {
|
|
22948
|
+
if (f.db_column === null) {
|
|
22949
|
+
const comp = f.ui_component;
|
|
22950
|
+
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
22951
|
+
const pType = registry.paragraph_types[comp.ref];
|
|
22952
|
+
if (!pType) continue;
|
|
22953
|
+
row[f.name] = oneOrMany(relationCardinality(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
|
|
22954
|
+
} else if (f.db_column.junction) {
|
|
22955
|
+
const j = f.db_column.junction;
|
|
22956
|
+
const r = await db.execute(
|
|
22957
|
+
sql`SELECT ${sql.raw(quoteIdent3(j.right_column))} FROM ${sql.raw(quoteIdent3(j.table_name))} WHERE ${sql.raw(quoteIdent3(j.left_column))} = ${id}`
|
|
22958
|
+
);
|
|
22959
|
+
row[f.name] = r.rows.map((linked) => linked[j.right_column]);
|
|
22960
|
+
}
|
|
22961
|
+
}
|
|
22962
|
+
}
|
|
22963
|
+
async function valuesForPublishCheck(db, registry, requiredFields, id, storedLabels, body) {
|
|
22964
|
+
const stored = { ...storedLabels };
|
|
22965
|
+
if (db) await loadRelationsForEdit(db, registry, requiredFields.filter((f) => !(f.name in body)), id, stored);
|
|
22966
|
+
return { ...stored, ...body };
|
|
22967
|
+
}
|
|
22968
|
+
async function persistRelationFields(db, registry, fields, body, itemId, ownerTable) {
|
|
22969
|
+
const deltas = [];
|
|
22970
|
+
for (const f of fields) {
|
|
22971
|
+
if (!(f.name in body)) continue;
|
|
22972
|
+
if (f.db_column === null) {
|
|
22973
|
+
const comp = f.ui_component;
|
|
22974
|
+
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
22975
|
+
const pType = registry.paragraph_types[comp.ref];
|
|
22976
|
+
if (!pType) continue;
|
|
22977
|
+
deltas.push(await persistParagraphField(db, itemId, ownerTable, f.name, pType, paragraphItems(f, body[f.name]), registry));
|
|
22978
|
+
} else if (f.db_column.junction) {
|
|
22979
|
+
const value = body[f.name];
|
|
22980
|
+
const relatedIds = Array.isArray(value) ? value.filter((v2) => typeof v2 === "string") : [];
|
|
22981
|
+
await persistJunctionField(db, itemId, f.db_column.junction, relatedIds);
|
|
22982
|
+
}
|
|
22983
|
+
}
|
|
22984
|
+
return deltas;
|
|
22985
|
+
}
|
|
22942
22986
|
async function lookupBasePathId(db, pathOrName) {
|
|
22943
22987
|
const r = await db.execute(
|
|
22944
22988
|
sql`SELECT id FROM base_paths WHERE path = ${pathOrName} OR name = ${pathOrName} LIMIT 1`
|
|
@@ -23094,26 +23138,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23094
23138
|
404
|
|
23095
23139
|
);
|
|
23096
23140
|
}
|
|
23097
|
-
if (db)
|
|
23098
|
-
const row = item;
|
|
23099
|
-
for (const f of contentType.fields) {
|
|
23100
|
-
if (f.db_column === null) {
|
|
23101
|
-
const comp = f.ui_component;
|
|
23102
|
-
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
23103
|
-
const pType = registry.paragraph_types[comp.ref];
|
|
23104
|
-
if (!pType) continue;
|
|
23105
|
-
row[f.name] = oneOrMany(relationCardinality(f) ?? "many", await loadParagraphRows(db, registry, pType, id, f.name));
|
|
23106
|
-
} else if (f.db_column.junction) {
|
|
23107
|
-
const j = f.db_column.junction;
|
|
23108
|
-
const r = await db.execute(
|
|
23109
|
-
sql`SELECT ${sql.raw(quoteIdent3(j.right_column))} FROM ${sql.raw(quoteIdent3(j.table_name))} WHERE ${sql.raw(quoteIdent3(j.left_column))} = ${id}`
|
|
23110
|
-
);
|
|
23111
|
-
row[f.name] = r.rows.map(
|
|
23112
|
-
(row2) => row2[j.right_column]
|
|
23113
|
-
);
|
|
23114
|
-
}
|
|
23115
|
-
}
|
|
23116
|
-
}
|
|
23141
|
+
if (db) await loadRelationsForEdit(db, registry, contentType.fields, id, item);
|
|
23117
23142
|
const data = projectRow(item, typeName, projectors);
|
|
23118
23143
|
return c.json({ ok: true, data });
|
|
23119
23144
|
}
|
|
@@ -23204,10 +23229,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23204
23229
|
});
|
|
23205
23230
|
if (publishDeny) return publishDeny;
|
|
23206
23231
|
}
|
|
23207
|
-
const paragraphFields = contentType.fields.filter((f) => f.db_column === null);
|
|
23208
|
-
const junctionFields = contentType.fields.filter(
|
|
23209
|
-
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
23210
|
-
);
|
|
23211
23232
|
const columnBackedFields = contentType.fields.filter(isColumnBacked);
|
|
23212
23233
|
const columnData = {
|
|
23213
23234
|
published: body["published"] ?? false
|
|
@@ -23242,19 +23263,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23242
23263
|
const itemId = item["id"];
|
|
23243
23264
|
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
|
|
23244
23265
|
if (db) {
|
|
23245
|
-
|
|
23246
|
-
const comp = f.ui_component;
|
|
23247
|
-
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
23248
|
-
const pType = registry.paragraph_types[comp.ref];
|
|
23249
|
-
if (!pType) continue;
|
|
23250
|
-
const items = paragraphItems(f, body[f.name]);
|
|
23251
|
-
mediaDeltas.push(await persistParagraphField(db, itemId, contentType.db.table_name, f.name, pType, items, registry));
|
|
23252
|
-
}
|
|
23253
|
-
for (const f of junctionFields) {
|
|
23254
|
-
const junction = f.db_column.junction;
|
|
23255
|
-
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v2) => typeof v2 === "string") : [];
|
|
23256
|
-
await persistJunctionField(db, itemId, junction, relatedIds);
|
|
23257
|
-
}
|
|
23266
|
+
mediaDeltas.push(...await persistRelationFields(db, registry, contentType.fields, body, itemId, contentType.db.table_name));
|
|
23258
23267
|
}
|
|
23259
23268
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
23260
23269
|
const data = projectRow(item, typeName, projectors);
|
|
@@ -23325,7 +23334,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23325
23334
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
23326
23335
|
});
|
|
23327
23336
|
if (publishDeny) return publishDeny;
|
|
23328
|
-
const merged =
|
|
23337
|
+
const merged = await valuesForPublishCheck(db, registry, requiredFields, id, fieldKeys.toLabels(existing), body);
|
|
23329
23338
|
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
23330
23339
|
if (fieldErrors.length > 0) {
|
|
23331
23340
|
return c.json(
|
|
@@ -23341,10 +23350,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23341
23350
|
);
|
|
23342
23351
|
}
|
|
23343
23352
|
}
|
|
23344
|
-
const patchParagraphFields = contentType.fields.filter((f) => f.db_column === null);
|
|
23345
|
-
const patchJunctionFields = contentType.fields.filter(
|
|
23346
|
-
(f) => f.db_column !== null && f.db_column.junction !== void 0
|
|
23347
|
-
);
|
|
23348
23353
|
const patchColumnBackedFields = contentType.fields.filter(isColumnBacked);
|
|
23349
23354
|
const patchData = {};
|
|
23350
23355
|
if (!contentType.only_one && "slug" in body) patchData["slug"] = body["slug"];
|
|
@@ -23365,21 +23370,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23365
23370
|
topLevelMediaDelta(mediaFields, existing, storageBody)
|
|
23366
23371
|
];
|
|
23367
23372
|
if (db) {
|
|
23368
|
-
|
|
23369
|
-
if (!(f.name in body)) continue;
|
|
23370
|
-
const comp = f.ui_component;
|
|
23371
|
-
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
23372
|
-
const pType = registry.paragraph_types[comp.ref];
|
|
23373
|
-
if (!pType) continue;
|
|
23374
|
-
const items = paragraphItems(f, body[f.name]);
|
|
23375
|
-
mediaDeltas.push(await persistParagraphField(db, id, contentType.db.table_name, f.name, pType, items, registry));
|
|
23376
|
-
}
|
|
23377
|
-
for (const f of patchJunctionFields) {
|
|
23378
|
-
if (!(f.name in body)) continue;
|
|
23379
|
-
const junction = f.db_column.junction;
|
|
23380
|
-
const relatedIds = Array.isArray(body[f.name]) ? body[f.name].filter((v2) => typeof v2 === "string") : [];
|
|
23381
|
-
await persistJunctionField(db, id, junction, relatedIds);
|
|
23382
|
-
}
|
|
23373
|
+
mediaDeltas.push(...await persistRelationFields(db, registry, contentType.fields, body, id, contentType.db.table_name));
|
|
23383
23374
|
}
|
|
23384
23375
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
23385
23376
|
const data = projectRow(updated, typeName, projectors);
|
|
@@ -23508,6 +23499,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23508
23499
|
404
|
|
23509
23500
|
);
|
|
23510
23501
|
}
|
|
23502
|
+
if (db) await loadRelationsForEdit(db, registry, taxonomyType.fields, id, item);
|
|
23511
23503
|
const data = projectRow(item, typeName, projectors);
|
|
23512
23504
|
return c.json({ ok: true, data });
|
|
23513
23505
|
}
|
|
@@ -23539,7 +23531,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23539
23531
|
});
|
|
23540
23532
|
if (publishDeny) return publishDeny;
|
|
23541
23533
|
}
|
|
23542
|
-
const taxParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
|
|
23543
23534
|
const taxColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
|
|
23544
23535
|
const taxColumnData = {
|
|
23545
23536
|
published: body["published"] ?? false
|
|
@@ -23553,14 +23544,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23553
23544
|
const taxItemId = item["id"];
|
|
23554
23545
|
const mediaDeltas = [topLevelMediaDelta(mediaFields, null, storageBody)];
|
|
23555
23546
|
if (db) {
|
|
23556
|
-
|
|
23557
|
-
const comp = f.ui_component;
|
|
23558
|
-
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
23559
|
-
const pType = registry.paragraph_types[comp.ref];
|
|
23560
|
-
if (!pType) continue;
|
|
23561
|
-
const items = paragraphItems(f, body[f.name]);
|
|
23562
|
-
mediaDeltas.push(await persistParagraphField(db, taxItemId, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
23563
|
-
}
|
|
23547
|
+
mediaDeltas.push(...await persistRelationFields(db, registry, taxonomyType.fields, body, taxItemId, taxonomyType.db.table_name));
|
|
23564
23548
|
}
|
|
23565
23549
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
23566
23550
|
const data = projectRow(item, typeName, projectors);
|
|
@@ -23587,7 +23571,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23587
23571
|
const publishDeny = await requirePermission("content:edit")(c, async () => {
|
|
23588
23572
|
});
|
|
23589
23573
|
if (publishDeny) return publishDeny;
|
|
23590
|
-
const merged =
|
|
23574
|
+
const merged = await valuesForPublishCheck(db, registry, requiredFields, id, fieldKeys.toLabels(existing), body);
|
|
23591
23575
|
const fieldErrors = checkRequiredFields(requiredFields, merged);
|
|
23592
23576
|
if (fieldErrors.length > 0) {
|
|
23593
23577
|
return c.json(
|
|
@@ -23603,7 +23587,6 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23603
23587
|
);
|
|
23604
23588
|
}
|
|
23605
23589
|
}
|
|
23606
|
-
const taxPatchParagraphFields = taxonomyType.fields.filter((f) => f.db_column === null);
|
|
23607
23590
|
const taxPatchColumnBackedFields = taxonomyType.fields.filter(isColumnBacked);
|
|
23608
23591
|
const taxPatchData = {};
|
|
23609
23592
|
if ("published" in body) taxPatchData["published"] = body["published"];
|
|
@@ -23623,15 +23606,7 @@ function registerAdminContentRoutes(app, registry, repos, projectors, mediaRepo,
|
|
|
23623
23606
|
topLevelMediaDelta(mediaFields, existing, storageBody)
|
|
23624
23607
|
];
|
|
23625
23608
|
if (db) {
|
|
23626
|
-
|
|
23627
|
-
if (!(f.name in body)) continue;
|
|
23628
|
-
const comp = f.ui_component;
|
|
23629
|
-
if (comp.component !== "paragraph-embed" || !comp.ref) continue;
|
|
23630
|
-
const pType = registry.paragraph_types[comp.ref];
|
|
23631
|
-
if (!pType) continue;
|
|
23632
|
-
const items = paragraphItems(f, body[f.name]);
|
|
23633
|
-
mediaDeltas.push(await persistParagraphField(db, id, taxonomyType.db.table_name, f.name, pType, items, registry));
|
|
23634
|
-
}
|
|
23609
|
+
mediaDeltas.push(...await persistRelationFields(db, registry, taxonomyType.fields, body, id, taxonomyType.db.table_name));
|
|
23635
23610
|
}
|
|
23636
23611
|
await applyMediaReferenceDelta(mergeMediaDeltas(...mediaDeltas), mediaRepo);
|
|
23637
23612
|
const data = projectRow(updated, typeName, projectors);
|
|
@@ -24619,7 +24594,7 @@ function registerUserRoutes(app, db, requirePermission, requireHierarchy) {
|
|
|
24619
24594
|
}
|
|
24620
24595
|
var API_VERSION = (() => {
|
|
24621
24596
|
try {
|
|
24622
|
-
return "0.8.
|
|
24597
|
+
return "0.8.2";
|
|
24623
24598
|
} catch {
|
|
24624
24599
|
return "0.0.0";
|
|
24625
24600
|
}
|
|
@@ -25302,7 +25277,7 @@ function createCmsApp(options) {
|
|
|
25302
25277
|
handler: null,
|
|
25303
25278
|
error: null
|
|
25304
25279
|
}));
|
|
25305
|
-
const ready = import("./handler-VEOWJRAL-
|
|
25280
|
+
const ready = import("./handler-VEOWJRAL-6WUFNANJ.js").then(({ createGraphQLHandler }) => {
|
|
25306
25281
|
gqlPasses.forEach((pass, i) => {
|
|
25307
25282
|
const mount = mounts[i];
|
|
25308
25283
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobbykim/manguito-cms-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Bobby Kim <bobby.sihun.kim@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/bobbykim89/manguito-cms",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"tsup": "^8.5.1",
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
31
|
"vite": "^8.1.3",
|
|
32
|
-
"@bobbykim/manguito-cms-admin": "0.5.
|
|
33
|
-
"@bobbykim/manguito-cms-
|
|
34
|
-
"@bobbykim/manguito-cms-db": "0.1.
|
|
35
|
-
"@bobbykim/manguito-cms-
|
|
32
|
+
"@bobbykim/manguito-cms-admin": "0.5.2",
|
|
33
|
+
"@bobbykim/manguito-cms-api": "0.8.2",
|
|
34
|
+
"@bobbykim/manguito-cms-db": "0.1.8",
|
|
35
|
+
"@bobbykim/manguito-cms-core": "0.8.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.9.5",
|