@dereekb/dbx-cli 13.15.0 → 13.16.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/firebase-api-manifest/main.js +147 -132
- package/firebase-api-manifest/package.json +3 -3
- package/generate-firestore-indexes/main.js +37 -24
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/main.js +49 -37
- package/generate-mcp-manifest/package.json +3 -3
- package/index.cjs.js +527 -321
- package/index.esm.js +527 -321
- package/lint-cache/package.json +2 -2
- package/manifest-extract/index.cjs.js +131 -118
- package/manifest-extract/index.esm.js +131 -118
- package/manifest-extract/package.json +2 -2
- package/package.json +6 -6
- package/test/index.cjs.js +1 -1
- package/test/index.esm.js +1 -1
- package/test/package.json +9 -9
|
@@ -284,29 +284,20 @@ function findFunctionsClassName(sourceFile) {
|
|
|
284
284
|
return result;
|
|
285
285
|
}
|
|
286
286
|
function inferGroupName(sourceFile) {
|
|
287
|
+
return findTypeAliasStem(sourceFile, "ModelCrudFunctionsConfig") ?? findTypeAliasStem(sourceFile, "FunctionTypeMap");
|
|
288
|
+
}
|
|
289
|
+
function findTypeAliasStem(sourceFile, ending) {
|
|
287
290
|
let result;
|
|
288
291
|
for (const alias of sourceFile.getTypeAliases()) {
|
|
289
292
|
const name = alias.getName();
|
|
290
|
-
if (name.endsWith(
|
|
291
|
-
const stem = name.slice(0, -
|
|
293
|
+
if (name.endsWith(ending)) {
|
|
294
|
+
const stem = name.slice(0, -ending.length);
|
|
292
295
|
if (stem.length > 0) {
|
|
293
296
|
result = stem;
|
|
294
297
|
break;
|
|
295
298
|
}
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
|
-
if (result === void 0) {
|
|
299
|
-
for (const alias of sourceFile.getTypeAliases()) {
|
|
300
|
-
const name = alias.getName();
|
|
301
|
-
if (name.endsWith("FunctionTypeMap")) {
|
|
302
|
-
const stem = name.slice(0, -"FunctionTypeMap".length);
|
|
303
|
-
if (stem.length > 0) {
|
|
304
|
-
result = stem;
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
301
|
return result;
|
|
311
302
|
}
|
|
312
303
|
function isNullLiteralType(node) {
|
|
@@ -402,44 +393,44 @@ function typeNodeName(node) {
|
|
|
402
393
|
return result;
|
|
403
394
|
}
|
|
404
395
|
function readTypeDocs(sourceFile, typeName) {
|
|
405
|
-
let result;
|
|
406
396
|
const interfaceDecl = sourceFile.getInterface(typeName);
|
|
397
|
+
let result;
|
|
407
398
|
if (interfaceDecl) {
|
|
408
|
-
|
|
409
|
-
const hasApiParamsTag = hasJsDocFlag(interfaceDecl, "dbxModelApiParams");
|
|
410
|
-
const fields = [];
|
|
411
|
-
for (const property of interfaceDecl.getProperties()) {
|
|
412
|
-
const fieldName = property.getName();
|
|
413
|
-
const description = readJsDocSummary(property);
|
|
414
|
-
const typeNode = property.getTypeNode();
|
|
415
|
-
const typeText = typeNode?.getText().trim() ?? "";
|
|
416
|
-
const adminOnly = hasJsDocFlag(property, "dbxModelApiAdminOnly");
|
|
417
|
-
const field = {
|
|
418
|
-
name: fieldName,
|
|
419
|
-
typeText,
|
|
420
|
-
...description ? { description } : {},
|
|
421
|
-
...adminOnly ? { accessLevel: "adminOnly" } : {}
|
|
422
|
-
};
|
|
423
|
-
fields.push(field);
|
|
424
|
-
}
|
|
425
|
-
if (typeDescription || fields.length > 0 || hasApiParamsTag) {
|
|
426
|
-
result = {
|
|
427
|
-
...typeDescription ? { typeDescription } : {},
|
|
428
|
-
...fields.length > 0 ? { fields } : {},
|
|
429
|
-
hasApiParamsTag
|
|
430
|
-
};
|
|
431
|
-
}
|
|
399
|
+
result = readInterfaceTypeDocs(interfaceDecl);
|
|
432
400
|
} else {
|
|
433
401
|
const typeAlias = sourceFile.getTypeAlias(typeName);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
402
|
+
result = typeAlias ? readTypeAliasDocs(typeAlias) : void 0;
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
function readInterfaceTypeDocs(interfaceDecl) {
|
|
407
|
+
const typeDescription = readJsDocSummary(interfaceDecl);
|
|
408
|
+
const hasApiParamsTag = hasJsDocFlag(interfaceDecl, "dbxModelApiParams");
|
|
409
|
+
const fields = interfaceDecl.getProperties().map((property) => readInterfaceField(property));
|
|
410
|
+
let result;
|
|
411
|
+
if (typeDescription || fields.length > 0 || hasApiParamsTag) {
|
|
412
|
+
result = {
|
|
413
|
+
...typeDescription ? { typeDescription } : {},
|
|
414
|
+
...fields.length > 0 ? { fields } : {},
|
|
415
|
+
hasApiParamsTag
|
|
416
|
+
};
|
|
440
417
|
}
|
|
441
418
|
return result;
|
|
442
419
|
}
|
|
420
|
+
function readInterfaceField(property) {
|
|
421
|
+
const description = readJsDocSummary(property);
|
|
422
|
+
const adminOnly = hasJsDocFlag(property, "dbxModelApiAdminOnly");
|
|
423
|
+
return {
|
|
424
|
+
name: property.getName(),
|
|
425
|
+
typeText: property.getTypeNode()?.getText().trim() ?? "",
|
|
426
|
+
...description ? { description } : {},
|
|
427
|
+
...adminOnly ? { accessLevel: "adminOnly" } : {}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function readTypeAliasDocs(typeAlias) {
|
|
431
|
+
const typeDescription = readJsDocSummary(typeAlias);
|
|
432
|
+
return typeDescription ? { typeDescription } : void 0;
|
|
433
|
+
}
|
|
443
434
|
function hasJsDocFlag(node, tagName) {
|
|
444
435
|
let result = false;
|
|
445
436
|
for (const doc of node.getJsDocs()) {
|
|
@@ -468,8 +459,8 @@ function readJsDocTagValue(node, tagName) {
|
|
|
468
459
|
function readJsDocSummary(node) {
|
|
469
460
|
let result;
|
|
470
461
|
const docs = node.getJsDocs();
|
|
471
|
-
|
|
472
|
-
|
|
462
|
+
const last = docs.at(-1);
|
|
463
|
+
if (last) {
|
|
473
464
|
const description = last.getDescription().trim();
|
|
474
465
|
if (description.length > 0) {
|
|
475
466
|
result = description;
|
|
@@ -762,38 +753,48 @@ function readNestedFromExpression(expr) {
|
|
|
762
753
|
if (Node3.isCallExpression(expr)) {
|
|
763
754
|
const fnName = expr.getExpression().getText();
|
|
764
755
|
if (fnName === SUB_OBJECT_FN || fnName === OBJECT_ARRAY_FN) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
inline: {
|
|
780
|
-
converterConst: void 0,
|
|
781
|
-
factory: fnName,
|
|
782
|
-
interfaceName: readGenericInterfaceName(expr),
|
|
783
|
-
fields: inlineFields,
|
|
784
|
-
line: expr.getStartLineNumber()
|
|
785
|
-
},
|
|
786
|
-
isArray
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
756
|
+
result = readNestedConverterCall(expr, fnName);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
function readNestedConverterCall(call, fnName) {
|
|
762
|
+
let result;
|
|
763
|
+
const args = call.getArguments();
|
|
764
|
+
if (args.length > 0) {
|
|
765
|
+
const config = args[0];
|
|
766
|
+
if (Node3.isObjectLiteralExpression(config)) {
|
|
767
|
+
const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
|
|
768
|
+
if (objectField) {
|
|
769
|
+
result = buildNestedConverterMatch({ objectField, call, fnName });
|
|
792
770
|
}
|
|
793
771
|
}
|
|
794
772
|
}
|
|
795
773
|
return result;
|
|
796
774
|
}
|
|
775
|
+
function buildNestedConverterMatch(input) {
|
|
776
|
+
const { objectField, call, fnName } = input;
|
|
777
|
+
const isArray = fnName === OBJECT_ARRAY_FN;
|
|
778
|
+
let result;
|
|
779
|
+
if (Node3.isIdentifier(objectField)) {
|
|
780
|
+
result = { ref: objectField.getText(), isArray };
|
|
781
|
+
} else if (Node3.isObjectLiteralExpression(objectField)) {
|
|
782
|
+
const fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
|
|
783
|
+
if (fieldsLiteral) {
|
|
784
|
+
result = {
|
|
785
|
+
inline: {
|
|
786
|
+
converterConst: void 0,
|
|
787
|
+
factory: fnName,
|
|
788
|
+
interfaceName: readGenericInterfaceName(call),
|
|
789
|
+
fields: readFieldEntries(fieldsLiteral),
|
|
790
|
+
line: call.getStartLineNumber()
|
|
791
|
+
},
|
|
792
|
+
isArray
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
797
798
|
function readPropertyValue(literal, key) {
|
|
798
799
|
const property = literal.getProperty(key);
|
|
799
800
|
let result;
|
|
@@ -1086,28 +1087,32 @@ function buildEntryForIdentity(input) {
|
|
|
1086
1087
|
depth: 0,
|
|
1087
1088
|
visitedConverters: /* @__PURE__ */ new Set()
|
|
1088
1089
|
});
|
|
1089
|
-
|
|
1090
|
-
const serviceFactory = registries.serviceFactoryByModelType.get(identity.modelType);
|
|
1091
|
-
result = {
|
|
1092
|
-
modelType: identity.modelType,
|
|
1093
|
-
modelName,
|
|
1094
|
-
...modelGroup ? { modelGroup } : {},
|
|
1095
|
-
identityConst: identity.identityConst,
|
|
1096
|
-
collectionPrefix: identity.collectionPrefix,
|
|
1097
|
-
...identity.parentIdentityConst ? { parentIdentityConst: identity.parentIdentityConst } : {},
|
|
1098
|
-
...iface.description ? { description: iface.description } : {},
|
|
1099
|
-
sourcePackage: source.sourcePackage,
|
|
1100
|
-
sourceFile: source.sourceFile,
|
|
1101
|
-
fields,
|
|
1102
|
-
...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
|
|
1103
|
-
...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
|
|
1104
|
-
...serviceFactory ? { serviceFactory } : {}
|
|
1105
|
-
};
|
|
1090
|
+
result = buildManifestEntry({ identity, modelName, collectionPrefix: identity.collectionPrefix, iface, fields, source, registries });
|
|
1106
1091
|
}
|
|
1107
1092
|
}
|
|
1108
1093
|
}
|
|
1109
1094
|
return result;
|
|
1110
1095
|
}
|
|
1096
|
+
function buildManifestEntry(input) {
|
|
1097
|
+
const { identity, modelName, collectionPrefix, iface, fields, source, registries } = input;
|
|
1098
|
+
const modelGroup = registries.groupByModelName.get(modelName);
|
|
1099
|
+
const serviceFactory = registries.serviceFactoryByModelType.get(identity.modelType);
|
|
1100
|
+
return {
|
|
1101
|
+
modelType: identity.modelType,
|
|
1102
|
+
modelName,
|
|
1103
|
+
...modelGroup ? { modelGroup } : {},
|
|
1104
|
+
identityConst: identity.identityConst,
|
|
1105
|
+
collectionPrefix,
|
|
1106
|
+
...identity.parentIdentityConst ? { parentIdentityConst: identity.parentIdentityConst } : {},
|
|
1107
|
+
...iface.description ? { description: iface.description } : {},
|
|
1108
|
+
sourcePackage: source.sourcePackage,
|
|
1109
|
+
sourceFile: source.sourceFile,
|
|
1110
|
+
fields,
|
|
1111
|
+
...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
|
|
1112
|
+
...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
|
|
1113
|
+
...serviceFactory ? { serviceFactory } : {}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1111
1116
|
function findConverterForInterface(extraction, interfaceName) {
|
|
1112
1117
|
return extraction.converters.find((c) => c.interfaceName === interfaceName);
|
|
1113
1118
|
}
|
|
@@ -1158,21 +1163,10 @@ function buildField(input) {
|
|
|
1158
1163
|
return out;
|
|
1159
1164
|
}
|
|
1160
1165
|
function resolveNestedFields(input) {
|
|
1161
|
-
const { field } = input;
|
|
1162
1166
|
let result;
|
|
1163
1167
|
if (input.depth < MAX_NESTED_DEPTH) {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
if (field.nestedConverterInline) {
|
|
1167
|
-
nestedConverter = field.nestedConverterInline;
|
|
1168
|
-
} else if (field.nestedConverterRef) {
|
|
1169
|
-
if (input.visitedConverters.has(field.nestedConverterRef)) {
|
|
1170
|
-
aborted = true;
|
|
1171
|
-
} else {
|
|
1172
|
-
nestedConverter = input.converterRegistry.get(field.nestedConverterRef);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
if (!aborted && nestedConverter) {
|
|
1168
|
+
const nestedConverter = selectNestedConverter(input);
|
|
1169
|
+
if (nestedConverter) {
|
|
1176
1170
|
const nextVisited = new Set(input.visitedConverters);
|
|
1177
1171
|
if (nestedConverter.converterConst) nextVisited.add(nestedConverter.converterConst);
|
|
1178
1172
|
const nestedIface = nestedConverter.interfaceName ? input.interfaceRegistry.get(nestedConverter.interfaceName) : void 0;
|
|
@@ -1185,11 +1179,21 @@ function resolveNestedFields(input) {
|
|
|
1185
1179
|
depth: input.depth + 1,
|
|
1186
1180
|
visitedConverters: nextVisited
|
|
1187
1181
|
});
|
|
1188
|
-
result = { fields, isArray: field.nestedIsArray ?? false };
|
|
1182
|
+
result = { fields, isArray: input.field.nestedIsArray ?? false };
|
|
1189
1183
|
}
|
|
1190
1184
|
}
|
|
1191
1185
|
return result;
|
|
1192
1186
|
}
|
|
1187
|
+
function selectNestedConverter(input) {
|
|
1188
|
+
const { field } = input;
|
|
1189
|
+
let result;
|
|
1190
|
+
if (field.nestedConverterInline) {
|
|
1191
|
+
result = field.nestedConverterInline;
|
|
1192
|
+
} else if (field.nestedConverterRef && !input.visitedConverters.has(field.nestedConverterRef)) {
|
|
1193
|
+
result = input.converterRegistry.get(field.nestedConverterRef);
|
|
1194
|
+
}
|
|
1195
|
+
return result;
|
|
1196
|
+
}
|
|
1193
1197
|
function collectAncestors(iface, registry) {
|
|
1194
1198
|
const out = [];
|
|
1195
1199
|
const visited = /* @__PURE__ */ new Set([iface.name]);
|
|
@@ -1261,31 +1265,42 @@ function findIdentifierInBarrelChain(filePath, identifier, visited) {
|
|
|
1261
1265
|
let result = false;
|
|
1262
1266
|
if (!visited.has(filePath)) {
|
|
1263
1267
|
visited.add(filePath);
|
|
1264
|
-
|
|
1265
|
-
try {
|
|
1266
|
-
text = readFileSync5(filePath, "utf8");
|
|
1267
|
-
} catch {
|
|
1268
|
-
text = void 0;
|
|
1269
|
-
}
|
|
1268
|
+
const text = readFileTextSafe(filePath);
|
|
1270
1269
|
if (text !== void 0) {
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1270
|
+
result = barrelTextDeclaresIdentifier(text, identifier) || reExportChainHasIdentifier({ fromFile: filePath, text, identifier, visited });
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return result;
|
|
1274
|
+
}
|
|
1275
|
+
function readFileTextSafe(filePath) {
|
|
1276
|
+
let text;
|
|
1277
|
+
try {
|
|
1278
|
+
text = readFileSync5(filePath, "utf8");
|
|
1279
|
+
} catch {
|
|
1280
|
+
text = void 0;
|
|
1281
|
+
}
|
|
1282
|
+
return text;
|
|
1283
|
+
}
|
|
1284
|
+
function barrelTextDeclaresIdentifier(text, identifier) {
|
|
1285
|
+
let result = false;
|
|
1286
|
+
for (const pattern of EXPORT_DECL_PATTERNS) {
|
|
1287
|
+
const re = new RegExp(pattern.source.replace("IDENT", escapeRegExp(identifier)));
|
|
1288
|
+
if (re.test(text)) {
|
|
1289
|
+
result = true;
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return result;
|
|
1294
|
+
}
|
|
1295
|
+
function reExportChainHasIdentifier(input) {
|
|
1296
|
+
const { fromFile, text, identifier, visited } = input;
|
|
1297
|
+
let result = false;
|
|
1298
|
+
const dir = dirname2(fromFile);
|
|
1299
|
+
for (const reExportTarget of collectReExportTargets(text)) {
|
|
1300
|
+
const resolved = resolveReExport(dir, reExportTarget);
|
|
1301
|
+
if (resolved && findIdentifierInBarrelChain(resolved, identifier, visited)) {
|
|
1302
|
+
result = true;
|
|
1303
|
+
break;
|
|
1289
1304
|
}
|
|
1290
1305
|
}
|
|
1291
1306
|
return result;
|
|
@@ -1402,7 +1417,7 @@ function renderEntry({ entry, validatorName }) {
|
|
|
1402
1417
|
entry.mcpResultTypeDescription ? `mcpResultTypeDescription: ${JSON.stringify(entry.mcpResultTypeDescription)}` : void 0,
|
|
1403
1418
|
entry.mcpResultFields && entry.mcpResultFields.length > 0 ? `mcpResultFields: ${renderDocFields(entry.mcpResultFields)}` : void 0
|
|
1404
1419
|
];
|
|
1405
|
-
return ` { ${fields.filter(
|
|
1420
|
+
return ` { ${fields.filter(Boolean).join(", ")} }`;
|
|
1406
1421
|
}
|
|
1407
1422
|
function renderDocFields(fields) {
|
|
1408
1423
|
const items = fields.map((field) => {
|
|
@@ -1432,7 +1447,7 @@ function renderModelEntry(entry, emitConverters) {
|
|
|
1432
1447
|
entry.read ? `read: ${JSON.stringify(entry.read)}` : void 0,
|
|
1433
1448
|
entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0
|
|
1434
1449
|
];
|
|
1435
|
-
return ` { ${fields.filter(
|
|
1450
|
+
return ` { ${fields.filter(Boolean).join(", ")} }`;
|
|
1436
1451
|
}
|
|
1437
1452
|
function renderModelFields(fields, emitConverters) {
|
|
1438
1453
|
let result;
|
|
@@ -1458,7 +1473,7 @@ function renderModelField(field, emitConverters) {
|
|
|
1458
1473
|
field.nestedFields ? `nestedFields: ${renderModelFields(field.nestedFields, emitConverters)}` : void 0,
|
|
1459
1474
|
field.nestedFields ? `nestedIsArray: ${nestedIsArrayLiteral}` : void 0
|
|
1460
1475
|
];
|
|
1461
|
-
return `{ ${parts.filter(
|
|
1476
|
+
return `{ ${parts.filter(Boolean).join(", ")} }`;
|
|
1462
1477
|
}
|
|
1463
1478
|
|
|
1464
1479
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/main.ts
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firebase-api-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.16.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"ts-morph": "^21.0.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@dereekb/dbx-cli": "13.
|
|
11
|
-
"@dereekb/util": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.16.0",
|
|
11
|
+
"@dereekb/util": "13.16.0",
|
|
12
12
|
"prettier": "3.8.3"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -5,14 +5,14 @@ const require = __createRequire(import.meta.url);
|
|
|
5
5
|
// packages/dbx-cli/generate-firestore-indexes/package.json
|
|
6
6
|
var package_default = {
|
|
7
7
|
name: "@dereekb/dbx-cli-generate-firestore-indexes",
|
|
8
|
-
version: "13.
|
|
8
|
+
version: "13.16.0",
|
|
9
9
|
private: true,
|
|
10
10
|
type: "module",
|
|
11
11
|
devDependencies: {
|
|
12
12
|
eslint: "10.4.0"
|
|
13
13
|
},
|
|
14
14
|
peerDependencies: {
|
|
15
|
-
"@dereekb/dbx-cli": "13.
|
|
15
|
+
"@dereekb/dbx-cli": "13.16.0"
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -2109,38 +2109,51 @@ function parseArgv(argv) {
|
|
|
2109
2109
|
return { component, output, check, json, help, error };
|
|
2110
2110
|
}
|
|
2111
2111
|
async function readExistingIndexes(input) {
|
|
2112
|
+
const text = await readExistingIndexesText(input);
|
|
2113
|
+
let result2;
|
|
2114
|
+
if (text !== void 0) {
|
|
2115
|
+
result2 = parseExistingIndexesJson(text, input.outputAbs, input.stderr);
|
|
2116
|
+
}
|
|
2117
|
+
return result2;
|
|
2118
|
+
}
|
|
2119
|
+
async function readExistingIndexesText(input) {
|
|
2112
2120
|
const { outputAbs, readFile, stderr } = input;
|
|
2113
|
-
let text
|
|
2114
|
-
let readFailed = false;
|
|
2121
|
+
let text;
|
|
2115
2122
|
try {
|
|
2116
2123
|
text = await readFile(outputAbs);
|
|
2117
2124
|
} catch (err) {
|
|
2118
|
-
readFailed = true;
|
|
2119
2125
|
const code = err.code;
|
|
2120
2126
|
if (code !== "ENOENT") {
|
|
2121
2127
|
stderr(`generate-firestore-indexes: could not read existing ${outputAbs}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2122
2128
|
}
|
|
2129
|
+
text = void 0;
|
|
2123
2130
|
}
|
|
2131
|
+
return text;
|
|
2132
|
+
}
|
|
2133
|
+
function parseExistingIndexesJson(text, outputAbs, stderr) {
|
|
2124
2134
|
let result2;
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2135
|
+
let parsed;
|
|
2136
|
+
let parseFailed = false;
|
|
2137
|
+
try {
|
|
2138
|
+
parsed = JSON.parse(text);
|
|
2139
|
+
} catch (err) {
|
|
2140
|
+
parseFailed = true;
|
|
2141
|
+
stderr(`generate-firestore-indexes: existing ${outputAbs} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
2142
|
+
}
|
|
2143
|
+
if (!parseFailed) {
|
|
2144
|
+
result2 = coerceFirestoreIndexesJson(parsed, outputAbs, stderr);
|
|
2145
|
+
}
|
|
2146
|
+
return result2;
|
|
2147
|
+
}
|
|
2148
|
+
function coerceFirestoreIndexesJson(parsed, outputAbs, stderr) {
|
|
2149
|
+
let result2;
|
|
2150
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
2151
|
+
stderr(`generate-firestore-indexes: existing ${outputAbs} top-level value is not an object`);
|
|
2152
|
+
} else {
|
|
2153
|
+
const raw = parsed;
|
|
2154
|
+
const indexes = Array.isArray(raw.indexes) ? raw.indexes : [];
|
|
2155
|
+
const fieldOverrides = Array.isArray(raw.fieldOverrides) ? raw.fieldOverrides : [];
|
|
2156
|
+
result2 = { indexes, fieldOverrides };
|
|
2144
2157
|
}
|
|
2145
2158
|
return result2;
|
|
2146
2159
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-firestore-indexes",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.16.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"eslint": "10.4.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@dereekb/dbx-cli": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.16.0"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -725,9 +725,7 @@ var EMPTY_ROLE_CONST_RESOLUTION = { roles: [], unresolved: [] };
|
|
|
725
725
|
function resolveRoleConstByName(name, ctx) {
|
|
726
726
|
let result;
|
|
727
727
|
const scalar = ctx.knownRoles.get(name);
|
|
728
|
-
if (scalar
|
|
729
|
-
result = { roles: [scalar], unresolved: [] };
|
|
730
|
-
} else {
|
|
728
|
+
if (scalar === void 0) {
|
|
731
729
|
const arrayExpr = ctx.localArrayRoles.get(name);
|
|
732
730
|
if (arrayExpr === void 0) {
|
|
733
731
|
result = { roles: [], unresolved: [name] };
|
|
@@ -738,6 +736,8 @@ function resolveRoleConstByName(name, ctx) {
|
|
|
738
736
|
nextSeen.add(name);
|
|
739
737
|
result = resolveRoleArrayLiteral(arrayExpr, { ...ctx, seen: nextSeen });
|
|
740
738
|
}
|
|
739
|
+
} else {
|
|
740
|
+
result = { roles: [scalar], unresolved: [] };
|
|
741
741
|
}
|
|
742
742
|
return result;
|
|
743
743
|
}
|
|
@@ -1015,53 +1015,65 @@ function renderMcpManifest(input, now = /* @__PURE__ */ new Date()) {
|
|
|
1015
1015
|
const warnings = [];
|
|
1016
1016
|
const errors = [];
|
|
1017
1017
|
const seenNames = /* @__PURE__ */ new Map();
|
|
1018
|
+
const segments = buildSegmentMap(input.modelManifest);
|
|
1019
|
+
const toolEntries = input.apiManifest.filter((entry) => entry.verb !== "standalone");
|
|
1020
|
+
const nameCounts = countToolNames(toolEntries, segments);
|
|
1021
|
+
for (const entry of toolEntries) {
|
|
1022
|
+
registerToolEntry({ entry, segments, nameCounts, tools, seenNames, warnings, errors });
|
|
1023
|
+
}
|
|
1024
|
+
const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
|
|
1025
|
+
const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
|
|
1026
|
+
const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
|
|
1027
|
+
const manifest = {
|
|
1028
|
+
...base,
|
|
1029
|
+
...models == null ? {} : { models },
|
|
1030
|
+
...auth == null ? {} : { auth }
|
|
1031
|
+
};
|
|
1032
|
+
return { manifest, warnings, errors };
|
|
1033
|
+
}
|
|
1034
|
+
function buildSegmentMap(modelManifest) {
|
|
1018
1035
|
const segments = /* @__PURE__ */ new Map();
|
|
1019
|
-
if (
|
|
1020
|
-
for (const model of
|
|
1036
|
+
if (modelManifest != null) {
|
|
1037
|
+
for (const model of modelManifest) {
|
|
1021
1038
|
if (model.mcpToolNameSegment != null && model.mcpToolNameSegment.length > 0) {
|
|
1022
1039
|
segments.set(model.modelType, model.mcpToolNameSegment);
|
|
1023
1040
|
}
|
|
1024
1041
|
}
|
|
1025
1042
|
}
|
|
1026
|
-
|
|
1043
|
+
return segments;
|
|
1044
|
+
}
|
|
1045
|
+
function countToolNames(toolEntries, segments) {
|
|
1027
1046
|
const nameCounts = /* @__PURE__ */ new Map();
|
|
1028
1047
|
for (const entry of toolEntries) {
|
|
1029
1048
|
const segment = segments.get(entry.model) ?? entry.model;
|
|
1030
1049
|
const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
|
|
1031
1050
|
nameCounts.set(baseName, (nameCounts.get(baseName) ?? 0) + 1);
|
|
1032
1051
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1052
|
+
return nameCounts;
|
|
1053
|
+
}
|
|
1054
|
+
function registerToolEntry(input) {
|
|
1055
|
+
const { entry, segments, nameCounts, tools, seenNames, warnings, errors } = input;
|
|
1056
|
+
const key = mcpManifestKey(entry.model, entry.verb, entry.specifier);
|
|
1057
|
+
tools[key] = buildToolEntry(entry);
|
|
1058
|
+
const segment = segments.get(entry.model) ?? entry.model;
|
|
1059
|
+
const baseName = buildMcpToolName(segment, entry.verb, entry.specifier);
|
|
1060
|
+
const clashes = (nameCounts.get(baseName) ?? 0) > 1;
|
|
1061
|
+
const toolName = clashes ? buildDisambiguatedMcpToolName(segment, entry.verb, entry.specifier) : baseName;
|
|
1062
|
+
if (toolName !== baseName) {
|
|
1063
|
+
warnings.push(`Tool name "${baseName}" is produced by more than one entry; re-derived as "${toolName}" (${key}) with the abbreviated call type to disambiguate.`);
|
|
1064
|
+
}
|
|
1065
|
+
const validation = validateMcpToolName(toolName);
|
|
1066
|
+
if (validation.level === "error") {
|
|
1067
|
+
errors.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_MAX_LENGTH}-char MCP cap (${key}). Shorten the model/specifier, set a per-model mcpToolNameSegment, or hide the tool.`);
|
|
1068
|
+
} else if (validation.level === "warn") {
|
|
1069
|
+
warnings.push(`Tool name "${toolName}" is ${validation.length} chars, over the ${MCP_TOOL_NAME_WARN_LENGTH}-char soft limit (${key}).`);
|
|
1070
|
+
}
|
|
1071
|
+
const priorKey = seenNames.get(toolName);
|
|
1072
|
+
if (priorKey == null) {
|
|
1073
|
+
seenNames.set(toolName, key);
|
|
1074
|
+
} else {
|
|
1075
|
+
warnings.push(`Tool name "${toolName}" is still produced by more than one entry (${priorKey} and ${key}) after disambiguation; one shadows the other unless hidden or renamed at runtime.`);
|
|
1055
1076
|
}
|
|
1056
|
-
const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
|
|
1057
|
-
const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
|
|
1058
|
-
const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
|
|
1059
|
-
const manifest = {
|
|
1060
|
-
...base,
|
|
1061
|
-
...models == null ? {} : { models },
|
|
1062
|
-
...auth == null ? {} : { auth }
|
|
1063
|
-
};
|
|
1064
|
-
return { manifest, warnings, errors };
|
|
1065
1077
|
}
|
|
1066
1078
|
function projectAuthSection(registry, appSlug) {
|
|
1067
1079
|
const primary = registry.findApp(appSlug);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-mcp-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.16.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@dereekb/dbx-cli": "13.
|
|
8
|
-
"@dereekb/model": "13.
|
|
7
|
+
"@dereekb/dbx-cli": "13.16.0",
|
|
8
|
+
"@dereekb/model": "13.16.0",
|
|
9
9
|
"arktype": "^2.2.0",
|
|
10
10
|
"jiti": "2.6.1"
|
|
11
11
|
}
|