@dereekb/dbx-cli 13.16.0 → 13.17.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.
Files changed (44) hide show
  1. package/eslint/index.cjs.default.js +1 -0
  2. package/eslint/index.cjs.js +1050 -0
  3. package/eslint/index.cjs.mjs +2 -0
  4. package/eslint/index.d.ts +1 -0
  5. package/eslint/index.esm.js +1046 -0
  6. package/eslint/package.json +25 -0
  7. package/eslint/rollup.alias-internal.config.d.ts +11 -0
  8. package/eslint/src/index.d.ts +1 -0
  9. package/eslint/src/lib/index.d.ts +2 -0
  10. package/eslint/src/lib/plugin.d.ts +22 -0
  11. package/eslint/src/lib/valid-dbx-route-model-tags.rule.d.ts +59 -0
  12. package/firebase-api-manifest/main.js +177 -102
  13. package/firebase-api-manifest/package.json +3 -3
  14. package/generate-firestore-indexes/main.js +2 -2
  15. package/generate-firestore-indexes/package.json +2 -2
  16. package/generate-mcp-manifest/main.js +8 -2
  17. package/generate-mcp-manifest/package.json +3 -3
  18. package/generate-route-manifest/main.js +1137 -0
  19. package/generate-route-manifest/package.json +10 -0
  20. package/index.cjs.js +4375 -1687
  21. package/index.esm.js +4355 -1688
  22. package/lint-cache/package.json +2 -2
  23. package/manifest-extract/index.cjs.js +54 -132
  24. package/manifest-extract/index.esm.js +53 -131
  25. package/manifest-extract/package.json +9 -4
  26. package/package.json +16 -6
  27. package/src/lib/index.d.ts +2 -0
  28. package/src/lib/manifest/types.d.ts +53 -0
  29. package/src/lib/mcp-scan/manifest/package-root.d.ts +17 -0
  30. package/src/lib/mcp-scan/manifest/tokens-schema.d.ts +5 -4
  31. package/src/lib/mcp-scan/scan/extract-models/assemble.d.ts +17 -0
  32. package/src/lib/route/component-resolve.d.ts +48 -0
  33. package/src/lib/route/index.d.ts +18 -0
  34. package/src/lib/route/route-build-tree.d.ts +31 -0
  35. package/src/lib/route/route-extract.d.ts +46 -0
  36. package/src/lib/route/route-load-tree.d.ts +17 -0
  37. package/src/lib/route/route-manifest.d.ts +132 -0
  38. package/src/lib/route/route-model-tag.d.ts +89 -0
  39. package/src/lib/route/route-models-extract.d.ts +22 -0
  40. package/src/lib/route/route-resolve-sources.d.ts +39 -0
  41. package/src/lib/route/route-types.d.ts +136 -0
  42. package/src/lib/route/url-match.d.ts +116 -0
  43. package/src/lib/scan-helpers/firestore-model-extract-utils.d.ts +43 -0
  44. package/test/package.json +9 -9
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@dereekb/dbx-cli/eslint",
3
+ "version": "13.17.0",
4
+ "peerDependencies": {
5
+ "@dereekb/dbx-cli": "13.17.0",
6
+ "@dereekb/util": "13.17.0",
7
+ "@typescript-eslint/utils": "8.59.3"
8
+ },
9
+ "devDependencies": {
10
+ "@typescript-eslint/parser": "8.59.3",
11
+ "eslint": "10.4.0"
12
+ },
13
+ "exports": {
14
+ "./package.json": "./package.json",
15
+ ".": {
16
+ "module": "./index.esm.js",
17
+ "types": "./index.d.ts",
18
+ "import": "./index.cjs.mjs",
19
+ "default": "./index.cjs.js"
20
+ }
21
+ },
22
+ "module": "./index.esm.js",
23
+ "main": "./index.cjs.js",
24
+ "types": "./index.d.ts"
25
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @param config - The rollup config @nx/rollup hands to the hook.
3
+ * @param _options - The @nx/rollup options (unused).
4
+ * @returns The mutated rollup config.
5
+ *
6
+ * @nx /rollup `rollupConfig` hook that inlines the workspace's internal `@dereekb/*` imports into
7
+ * the published ESLint plugin bundle. Prepends an alias plugin redirecting {@link INTERNAL_ALIASES}
8
+ * to their TS sources and wraps the `external: "all"` callback so {@link BUNDLED_DEPENDENCIES} are
9
+ * treated as internal.
10
+ */
11
+ export default function applyInternalAliases(config: any, _options: any): Promise<any>;
@@ -0,0 +1 @@
1
+ export * from './lib';
@@ -0,0 +1,2 @@
1
+ export { DBX_CLI_VALID_DBX_ROUTE_MODEL_TAGS_RULE, type DbxCliValidDbxRouteModelTagsRuleDefinition } from './valid-dbx-route-model-tags.rule';
2
+ export { DBX_CLI_ESLINT_PLUGIN, dbxCliESLintPlugin, type DbxCliEslintPlugin } from './plugin';
@@ -0,0 +1,22 @@
1
+ import { type DbxCliValidDbxRouteModelTagsRuleDefinition } from './valid-dbx-route-model-tags.rule';
2
+ /**
3
+ * ESLint plugin interface for `@dereekb/dbx-cli` rules.
4
+ */
5
+ export interface DbxCliEslintPlugin {
6
+ readonly rules: {
7
+ readonly 'valid-dbx-route-model-tags': DbxCliValidDbxRouteModelTagsRuleDefinition;
8
+ };
9
+ }
10
+ /**
11
+ * ESLint plugin for `@dereekb/dbx-cli` rules.
12
+ *
13
+ * Register as a plugin in your flat ESLint config, then enable individual rules
14
+ * under the chosen plugin prefix (e.g. 'dereekb-dbx-cli/valid-dbx-route-model-tags').
15
+ */
16
+ export declare const DBX_CLI_ESLINT_PLUGIN: DbxCliEslintPlugin;
17
+ /**
18
+ * camelCase alias of {@link DBX_CLI_ESLINT_PLUGIN} matching the conventional ESLint plugin export name.
19
+ *
20
+ * @dbxAllowConstantName
21
+ */
22
+ export declare const dbxCliESLintPlugin: DbxCliEslintPlugin;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ESLint rule that validates `@dbxRouteModel` / `@dbxRouteModelList` JSDoc tag
3
+ * grammar at author time. Each tag is parsed through `@dereekb/dbx-cli`'s
4
+ * canonical {@link parseRouteModelTag} grammar — the exact parser the build-time
5
+ * route-manifest builder uses — so ESLint and the manifest generator can never
6
+ * disagree about what a valid tag is. A malformed tag (typo'd model type, bad
7
+ * key template, wrong token count, unknown `@dbxRouteModel*` name) is reported on
8
+ * its own JSDoc line with the parser's failure reason.
9
+ *
10
+ * Scope: route-model tags live on `@Component` classes in `*.component.ts` and on
11
+ * exported `Ng2StateDeclaration` consts in `*.router.ts`, so the rule visits class
12
+ * and variable declarations (anchoring to their `export` wrapper for the leading
13
+ * JSDoc). It has no auto-fix — the correct value requires human / agent judgment.
14
+ */
15
+ interface AstNode {
16
+ readonly type: string;
17
+ [key: string]: any;
18
+ }
19
+ /**
20
+ * ESLint rule definition for valid-dbx-route-model-tags.
21
+ */
22
+ export interface DbxCliValidDbxRouteModelTagsRuleDefinition {
23
+ readonly meta: {
24
+ readonly type: 'problem';
25
+ readonly docs: {
26
+ readonly description: string;
27
+ readonly recommended: boolean;
28
+ };
29
+ readonly messages: Readonly<Record<string, string>>;
30
+ readonly schema: readonly object[];
31
+ };
32
+ create(context: {
33
+ report: (descriptor: {
34
+ loc?: AstNode;
35
+ node?: AstNode;
36
+ messageId: string;
37
+ data?: Record<string, string>;
38
+ }) => void;
39
+ sourceCode: AstNode;
40
+ }): Record<string, (node: AstNode) => void>;
41
+ }
42
+ /**
43
+ * ESLint rule enforcing that `@dbxRouteModel` / `@dbxRouteModelList` tags parse
44
+ * cleanly through the canonical route-model grammar. Reuses
45
+ * {@link parseRouteModelTag} from `@dereekb/dbx-cli` so a tag that lints clean is
46
+ * a tag the build-time manifest builder will accept.
47
+ *
48
+ * Checks (delegated to the grammar parser):
49
+ *
50
+ * - `@dbxRouteModel <modelType> <keyTemplate>` has exactly two tokens with a valid
51
+ * model-type identifier and a parseable key template (`:param` / `{authUid}` /
52
+ * even-segment `gb/:id/...` form).
53
+ * - `@dbxRouteModelList <modelType>` has a single valid model-type token.
54
+ * - A `@dbxRouteModel*` tag with an unknown name is flagged.
55
+ *
56
+ * Report-only — no auto-fix, since the corrected value needs judgment.
57
+ */
58
+ export declare const DBX_CLI_VALID_DBX_ROUTE_MODEL_TAGS_RULE: DbxCliValidDbxRouteModelTagsRuleDefinition;
59
+ export {};
@@ -469,14 +469,92 @@ function readJsDocSummary(node) {
469
469
  return result;
470
470
  }
471
471
 
472
+ // packages/util/src/lib/string/sort.ts
473
+ var compareStrings = (a, b) => a.localeCompare(b);
474
+
475
+ // packages/dbx-cli/src/lib/scan-helpers/firestore-model-extract-utils.ts
476
+ import { Node as Node3 } from "ts-morph";
477
+ var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
478
+ function parseFirestoreModelIdentityArgs(args) {
479
+ let result;
480
+ if (args.length === 1) {
481
+ const modelType = stringLiteralValue(args[0]);
482
+ if (modelType !== void 0) {
483
+ result = { modelType, collectionPrefix: void 0, parentIdentityConst: void 0 };
484
+ }
485
+ } else if (args.length === 2) {
486
+ const first = stringLiteralValue(args[0]);
487
+ if (first === void 0) {
488
+ const modelType = stringLiteralValue(args[1]);
489
+ if (modelType !== void 0) {
490
+ result = { modelType, collectionPrefix: void 0, parentIdentityConst: identifierName(args[0]) };
491
+ }
492
+ } else {
493
+ result = { modelType: first, collectionPrefix: stringLiteralValue(args[1]), parentIdentityConst: void 0 };
494
+ }
495
+ } else if (args.length >= 3) {
496
+ const modelType = stringLiteralValue(args[1]);
497
+ if (modelType !== void 0) {
498
+ result = { modelType, collectionPrefix: stringLiteralValue(args[2]), parentIdentityConst: identifierName(args[0]) };
499
+ }
500
+ }
501
+ return result;
502
+ }
503
+ function resolveExtendsName(expr) {
504
+ const head = expr.getExpression().getText();
505
+ let result = head;
506
+ if (PASSTHROUGH_TYPE_WRAPPERS.has(head)) {
507
+ const typeArgs = expr.getTypeArguments();
508
+ if (typeArgs.length > 0) {
509
+ const peeled = peelTypeNode(typeArgs[0]);
510
+ if (peeled !== void 0) {
511
+ result = peeled;
512
+ }
513
+ }
514
+ }
515
+ return result;
516
+ }
517
+ function peelTypeNode(node) {
518
+ let current = node;
519
+ while (Node3.isParenthesizedTypeNode(current)) {
520
+ current = current.getTypeNode();
521
+ }
522
+ let result;
523
+ if (Node3.isTypeReference(current)) {
524
+ const name = current.getTypeName().getText();
525
+ if (PASSTHROUGH_TYPE_WRAPPERS.has(name)) {
526
+ const inner = current.getTypeArguments();
527
+ if (inner.length > 0) {
528
+ result = peelTypeNode(inner[0]);
529
+ }
530
+ } else {
531
+ result = name;
532
+ }
533
+ }
534
+ return result;
535
+ }
536
+ function stringLiteralValue(node) {
537
+ let result;
538
+ if (Node3.isStringLiteral(node) || Node3.isNoSubstitutionTemplateLiteral(node)) {
539
+ result = node.getLiteralText();
540
+ }
541
+ return result;
542
+ }
543
+ function identifierName(node) {
544
+ let result;
545
+ if (Node3.isIdentifier(node)) {
546
+ result = node.getText();
547
+ }
548
+ return result;
549
+ }
550
+
472
551
  // packages/dbx-cli/manifest-extract/src/lib/extract-models.ts
473
- import { Node as Node3, Project as Project3 } from "ts-morph";
552
+ import { Node as Node4, Project as Project3 } from "ts-morph";
474
553
  var READ_LEVEL_VALUES = /* @__PURE__ */ new Set(["system", "owner", "admin-only", "permissions"]);
475
554
  var SERVICE_FACTORY_TAG = "dbxModelServiceFactory";
476
555
  var MCP_TOOL_NAME_SEGMENT_TAG = "dbxModelMcpToolNameSegment";
477
556
  var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
478
557
  var TOOL_NAME_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_$]*$/;
479
- var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
480
558
  var IDENTITY_FN = "firestoreModelIdentity";
481
559
  var CONVERTER_FN_NAMES = ["snapshotConverterFunctions", "firestoreSubObject", "firestoreObjectArray"];
482
560
  var SUB_OBJECT_FN = "firestoreSubObject";
@@ -484,6 +562,7 @@ var OBJECT_ARRAY_FN = "firestoreObjectArray";
484
562
  var SNAPSHOT_FN = "snapshotConverterFunctions";
485
563
  var FIELDS_LITERAL_KEY = "fields";
486
564
  var OBJECT_FIELD_KEY = "objectField";
565
+ var FIRESTORE_FIELD_KEY = "firestoreField";
487
566
  function extractModelsFromSource(input) {
488
567
  const project = new Project3({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
489
568
  const sourceFile = project.createSourceFile(input.name, input.text, { overwrite: true });
@@ -501,9 +580,9 @@ function readIdentities(sourceFile) {
501
580
  if (!statement.isExported()) continue;
502
581
  for (const decl of statement.getDeclarations()) {
503
582
  const initializer = decl.getInitializer();
504
- if (!initializer || !Node3.isCallExpression(initializer)) continue;
583
+ if (!initializer || !Node4.isCallExpression(initializer)) continue;
505
584
  if (initializer.getExpression().getText() !== IDENTITY_FN) continue;
506
- const parsed = parseIdentityArgs(initializer);
585
+ const parsed = parseFirestoreModelIdentityArgs(initializer.getArguments());
507
586
  if (parsed) {
508
587
  out.push({ identityConst: decl.getName(), ...parsed });
509
588
  }
@@ -511,32 +590,6 @@ function readIdentities(sourceFile) {
511
590
  }
512
591
  return out;
513
592
  }
514
- function parseIdentityArgs(call) {
515
- const args = call.getArguments();
516
- let result;
517
- if (args.length === 1) {
518
- const modelType = stringLiteralValue(args[0]);
519
- if (modelType !== void 0) {
520
- result = { modelType, collectionPrefix: void 0, parentIdentityConst: void 0 };
521
- }
522
- } else if (args.length === 2) {
523
- const first = stringLiteralValue(args[0]);
524
- if (first === void 0) {
525
- const modelType = stringLiteralValue(args[1]);
526
- if (modelType !== void 0) {
527
- result = { modelType, collectionPrefix: void 0, parentIdentityConst: identifierName(args[0]) };
528
- }
529
- } else {
530
- result = { modelType: first, collectionPrefix: stringLiteralValue(args[1]), parentIdentityConst: void 0 };
531
- }
532
- } else if (args.length >= 3) {
533
- const modelType = stringLiteralValue(args[1]);
534
- if (modelType !== void 0) {
535
- result = { modelType, collectionPrefix: stringLiteralValue(args[2]), parentIdentityConst: identifierName(args[0]) };
536
- }
537
- }
538
- return result;
539
- }
540
593
  function readInterfaces(sourceFile) {
541
594
  const out = [];
542
595
  for (const decl of sourceFile.getInterfaces()) {
@@ -637,46 +690,13 @@ function readServiceFactoryModelType(jsDocs) {
637
690
  }
638
691
  return result;
639
692
  }
640
- function resolveExtendsName(expr) {
641
- const head = expr.getExpression().getText();
642
- let result = head;
643
- if (PASSTHROUGH_TYPE_WRAPPERS.has(head)) {
644
- const typeArgs = expr.getTypeArguments();
645
- if (typeArgs.length > 0) {
646
- const peeled = peelTypeNode(typeArgs[0]);
647
- if (peeled !== void 0) {
648
- result = peeled;
649
- }
650
- }
651
- }
652
- return result;
653
- }
654
- function peelTypeNode(node) {
655
- let current = node;
656
- while (Node3.isParenthesizedTypeNode(current)) {
657
- current = current.getTypeNode();
658
- }
659
- let result;
660
- if (Node3.isTypeReference(current)) {
661
- const name = current.getTypeName().getText();
662
- if (PASSTHROUGH_TYPE_WRAPPERS.has(name)) {
663
- const inner = current.getTypeArguments();
664
- if (inner.length > 0) {
665
- result = peelTypeNode(inner[0]);
666
- }
667
- } else {
668
- result = name;
669
- }
670
- }
671
- return result;
672
- }
673
693
  function readConverters(sourceFile) {
674
694
  const out = [];
675
695
  for (const statement of sourceFile.getVariableStatements()) {
676
696
  if (!statement.isExported()) continue;
677
697
  for (const decl of statement.getDeclarations()) {
678
698
  const initializer = decl.getInitializer();
679
- if (!initializer || !Node3.isCallExpression(initializer)) continue;
699
+ if (!initializer || !Node4.isCallExpression(initializer)) continue;
680
700
  const factory = initializer.getExpression().getText();
681
701
  if (!isConverterFactoryName(factory)) continue;
682
702
  const interfaceName = readGenericInterfaceName(initializer);
@@ -710,13 +730,13 @@ function readConverterFields(call) {
710
730
  let result;
711
731
  if (args.length > 0) {
712
732
  const config = args[0];
713
- if (Node3.isObjectLiteralExpression(config)) {
733
+ if (Node4.isObjectLiteralExpression(config)) {
714
734
  let fieldsLiteral;
715
735
  if (fnName === SNAPSHOT_FN) {
716
736
  fieldsLiteral = readObjectProperty(config, FIELDS_LITERAL_KEY);
717
737
  } else {
718
738
  const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
719
- if (objectField && Node3.isObjectLiteralExpression(objectField)) {
739
+ if (objectField && Node4.isObjectLiteralExpression(objectField)) {
720
740
  fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
721
741
  }
722
742
  }
@@ -730,7 +750,7 @@ function readConverterFields(call) {
730
750
  function readFieldEntries(fields) {
731
751
  const out = [];
732
752
  for (const property of fields.getProperties()) {
733
- if (Node3.isPropertyAssignment(property)) {
753
+ if (Node4.isPropertyAssignment(property)) {
734
754
  const initializer = property.getInitializer();
735
755
  const converterText = initializer ? initializer.getText().replaceAll(/\s+/g, " ").trim() : "";
736
756
  const nested = initializer ? readNestedFromExpression(initializer) : void 0;
@@ -741,7 +761,7 @@ function readFieldEntries(fields) {
741
761
  nestedConverterInline: nested?.inline,
742
762
  nestedIsArray: nested?.isArray
743
763
  });
744
- } else if (Node3.isShorthandPropertyAssignment(property)) {
764
+ } else if (Node4.isShorthandPropertyAssignment(property)) {
745
765
  const name = property.getName();
746
766
  out.push({ key: name, converter: name });
747
767
  }
@@ -750,7 +770,7 @@ function readFieldEntries(fields) {
750
770
  }
751
771
  function readNestedFromExpression(expr) {
752
772
  let result;
753
- if (Node3.isCallExpression(expr)) {
773
+ if (Node4.isCallExpression(expr)) {
754
774
  const fnName = expr.getExpression().getText();
755
775
  if (fnName === SUB_OBJECT_FN || fnName === OBJECT_ARRAY_FN) {
756
776
  result = readNestedConverterCall(expr, fnName);
@@ -763,10 +783,10 @@ function readNestedConverterCall(call, fnName) {
763
783
  const args = call.getArguments();
764
784
  if (args.length > 0) {
765
785
  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 });
786
+ if (Node4.isObjectLiteralExpression(config)) {
787
+ const valueNode = readPropertyValue(config, OBJECT_FIELD_KEY) ?? readPropertyValue(config, FIRESTORE_FIELD_KEY);
788
+ if (valueNode) {
789
+ result = buildNestedConverterMatch({ objectField: valueNode, call, fnName });
770
790
  }
771
791
  }
772
792
  }
@@ -776,9 +796,9 @@ function buildNestedConverterMatch(input) {
776
796
  const { objectField, call, fnName } = input;
777
797
  const isArray = fnName === OBJECT_ARRAY_FN;
778
798
  let result;
779
- if (Node3.isIdentifier(objectField)) {
799
+ if (Node4.isIdentifier(objectField)) {
780
800
  result = { ref: objectField.getText(), isArray };
781
- } else if (Node3.isObjectLiteralExpression(objectField)) {
801
+ } else if (Node4.isObjectLiteralExpression(objectField)) {
782
802
  const fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
783
803
  if (fieldsLiteral) {
784
804
  result = {
@@ -792,22 +812,27 @@ function buildNestedConverterMatch(input) {
792
812
  isArray
793
813
  };
794
814
  }
815
+ } else if (Node4.isCallExpression(objectField)) {
816
+ const nested = readNestedFromExpression(objectField);
817
+ if (nested) {
818
+ result = { ...nested, isArray };
819
+ }
795
820
  }
796
821
  return result;
797
822
  }
798
823
  function readPropertyValue(literal, key) {
799
824
  const property = literal.getProperty(key);
800
825
  let result;
801
- if (property && Node3.isPropertyAssignment(property)) {
826
+ if (property && Node4.isPropertyAssignment(property)) {
802
827
  result = property.getInitializer();
803
- } else if (property && Node3.isShorthandPropertyAssignment(property)) {
828
+ } else if (property && Node4.isShorthandPropertyAssignment(property)) {
804
829
  result = property.getNameNode();
805
830
  }
806
831
  return result;
807
832
  }
808
833
  function readObjectProperty(literal, key) {
809
834
  const value = readPropertyValue(literal, key);
810
- return value && Node3.isObjectLiteralExpression(value) ? value : void 0;
835
+ return value && Node4.isObjectLiteralExpression(value) ? value : void 0;
811
836
  }
812
837
  function readEnums(sourceFile) {
813
838
  const out = [];
@@ -906,20 +931,6 @@ function firstParagraph(text) {
906
931
  }
907
932
  return collected.join(" ").trim();
908
933
  }
909
- function stringLiteralValue(node) {
910
- let result;
911
- if (Node3.isStringLiteral(node) || Node3.isNoSubstitutionTemplateLiteral(node)) {
912
- result = node.getLiteralText();
913
- }
914
- return result;
915
- }
916
- function identifierName(node) {
917
- let result;
918
- if (Node3.isIdentifier(node)) {
919
- result = node.getText();
920
- }
921
- return result;
922
- }
923
934
 
924
935
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-api-files.ts
925
936
  function findApiFiles(packageRoot) {
@@ -1022,6 +1033,47 @@ function assembleModels(input) {
1022
1033
  accumulator.entries.sort((a, b) => a.modelType.localeCompare(b.modelType));
1023
1034
  return accumulator.entries;
1024
1035
  }
1036
+ function collectModelEnums(input) {
1037
+ const registry = buildEnumRegistry(input.extractions);
1038
+ const referenced = collectReferencedEnumNames(input.models);
1039
+ const out = {};
1040
+ for (const name of [...referenced].sort((a, b) => a.localeCompare(b))) {
1041
+ const extracted = registry.get(name);
1042
+ if (extracted) {
1043
+ out[name] = buildModelEnumEntry(extracted);
1044
+ }
1045
+ }
1046
+ return out;
1047
+ }
1048
+ function buildEnumRegistry(extractions) {
1049
+ const registry = /* @__PURE__ */ new Map();
1050
+ for (const { extraction } of extractions) {
1051
+ for (const e of extraction.enums) {
1052
+ if (!registry.has(e.name)) registry.set(e.name, e);
1053
+ }
1054
+ }
1055
+ return registry;
1056
+ }
1057
+ function collectReferencedEnumNames(models) {
1058
+ const referenced = /* @__PURE__ */ new Set();
1059
+ for (const model of models) {
1060
+ addReferencedEnumNames(model.fields, referenced);
1061
+ }
1062
+ return referenced;
1063
+ }
1064
+ function addReferencedEnumNames(fields, referenced) {
1065
+ for (const field of fields) {
1066
+ if (field.enumRef) referenced.add(field.enumRef);
1067
+ if (field.nestedFields) addReferencedEnumNames(field.nestedFields, referenced);
1068
+ }
1069
+ }
1070
+ function buildModelEnumEntry(e) {
1071
+ return {
1072
+ name: e.name,
1073
+ values: e.values.map((v) => v.description ? { name: v.name, value: v.value, description: v.description } : { name: v.name, value: v.value }),
1074
+ ...e.description ? { description: e.description } : {}
1075
+ };
1076
+ }
1025
1077
  function buildGlobalRegistries(extractions) {
1026
1078
  const converterRegistry = /* @__PURE__ */ new Map();
1027
1079
  const interfaceRegistry = /* @__PURE__ */ new Map();
@@ -1358,13 +1410,8 @@ function escapeRegExp(value) {
1358
1410
 
1359
1411
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/emit.ts
1360
1412
  import { format, resolveConfig } from "prettier";
1361
-
1362
- // packages/util/src/lib/string/sort.ts
1363
- var compareStrings = (a, b) => a.localeCompare(b);
1364
-
1365
- // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/emit.ts
1366
1413
  async function renderManifest(input) {
1367
- const { outputFile, entries, projectName, namespace, modelEntries, modelNamespace, emitConverters = false } = input;
1414
+ const { outputFile, entries, projectName, namespace, modelEntries, modelNamespace, enumEntries, enumNamespace, emitConverters = false } = input;
1368
1415
  const importsByPackage = /* @__PURE__ */ new Map();
1369
1416
  for (const entry of entries) {
1370
1417
  if (!entry.packageName || !entry.validatorName) continue;
@@ -1378,12 +1425,18 @@ async function renderManifest(input) {
1378
1425
  });
1379
1426
  const entryLines = entries.map((e) => renderEntry(e));
1380
1427
  const emitModels = Boolean(modelEntries && modelEntries.length > 0 && modelNamespace);
1381
- const dbxCliTypeImports = emitModels ? `import { type CliApiManifest, type CliModelManifest } from '@dereekb/dbx-cli';` : `import { type CliApiManifest } from '@dereekb/dbx-cli';`;
1428
+ const emitEnums = Boolean(enumEntries && Object.keys(enumEntries).length > 0 && enumNamespace);
1429
+ const dbxCliTypeNames = ["type CliApiManifest", ...emitModels ? ["type CliModelManifest"] : [], ...emitEnums ? ["type CliEnumManifest"] : []];
1430
+ const dbxCliTypeImports = `import { ${dbxCliTypeNames.join(", ")} } from '@dereekb/dbx-cli';`;
1382
1431
  const modelSection = emitModels ? `
1383
1432
 
1384
1433
  export const ${modelNamespace}: CliModelManifest = [
1385
1434
  ${(modelEntries ?? []).map((m) => renderModelEntry(m, emitConverters)).join(",\n")}
1386
1435
  ];
1436
+ ` : "";
1437
+ const enumSection = emitEnums ? `
1438
+
1439
+ export const ${enumNamespace}: CliEnumManifest = ${renderEnumManifest(enumEntries ?? {})};
1387
1440
  ` : "";
1388
1441
  const source = `/* eslint-disable @nx/enforce-module-boundaries */
1389
1442
  // AUTO-GENERATED \u2014 DO NOT EDIT.
@@ -1394,7 +1447,7 @@ ${dbxCliTypeImports}
1394
1447
 
1395
1448
  export const ${namespace}: CliApiManifest = [
1396
1449
  ${entryLines.join(",\n")}
1397
- ];${modelSection}
1450
+ ];${modelSection}${enumSection}
1398
1451
  `;
1399
1452
  return formatWithPrettier(source, outputFile);
1400
1453
  }
@@ -1459,6 +1512,23 @@ function renderModelFields(fields, emitConverters) {
1459
1512
  }
1460
1513
  return result;
1461
1514
  }
1515
+ function renderEnumManifest(enums) {
1516
+ const names = Object.keys(enums).sort((a, b) => a.localeCompare(b));
1517
+ const items = names.map((name) => `${JSON.stringify(name)}: ${renderEnumEntry(enums[name])}`);
1518
+ return names.length === 0 ? "{}" : `{ ${items.join(", ")} }`;
1519
+ }
1520
+ function renderEnumEntry(entry) {
1521
+ const parts = [`name: ${JSON.stringify(entry.name)}`, `values: ${renderEnumValues(entry.values)}`, entry.description ? `description: ${JSON.stringify(entry.description)}` : void 0];
1522
+ return `{ ${parts.filter(Boolean).join(", ")} }`;
1523
+ }
1524
+ function renderEnumValues(values) {
1525
+ const items = values.map((value) => renderEnumValue(value));
1526
+ return `[${items.join(", ")}]`;
1527
+ }
1528
+ function renderEnumValue(value) {
1529
+ const parts = [`name: ${JSON.stringify(value.name)}`, `value: ${JSON.stringify(value.value)}`, value.description ? `description: ${JSON.stringify(value.description)}` : void 0];
1530
+ return `{ ${parts.filter(Boolean).join(", ")} }`;
1531
+ }
1462
1532
  function renderModelField(field, emitConverters) {
1463
1533
  const nestedIsArrayLiteral = field.nestedIsArray ? "true" : "false";
1464
1534
  const parts = [
@@ -1566,8 +1636,9 @@ async function main() {
1566
1636
  collected.sort(compareEntries);
1567
1637
  const modelEntries = flags.emitModels ? assembleModels({ extractions: modelSources }) : [];
1568
1638
  const filteredModelEntries = flags.only ? modelEntries.filter((m) => flags.only?.has(m.modelType)) : modelEntries;
1639
+ const enumEntries = flags.emitModels ? collectModelEnums({ extractions: modelSources, models: filteredModelEntries }) : {};
1569
1640
  ensureOutputDir(outputDir);
1570
- const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace, modelEntries: filteredModelEntries, modelNamespace: deriveModelNamespace(flags.project), emitConverters: flags.emitModelConverters });
1641
+ const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace, modelEntries: filteredModelEntries, modelNamespace: deriveModelNamespace(flags.project), enumEntries, enumNamespace: deriveEnumNamespace(flags.project), emitConverters: flags.emitModelConverters });
1571
1642
  if (existsSync3(outputFile) && readFileSync6(outputFile, "utf8") === formatted) {
1572
1643
  console.log(`[unchanged] ${relative2(WORKSPACE_ROOT, outputFile)}`);
1573
1644
  } else {
@@ -1575,7 +1646,7 @@ async function main() {
1575
1646
  console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputFile)}`);
1576
1647
  }
1577
1648
  const groupCount = packageCache.size === 0 ? 0 : new Set(collected.map((c) => c.entry.groupName)).size;
1578
- const modelSummary = flags.emitModels ? ` \xB7 ${filteredModelEntries.length} models` : "";
1649
+ const modelSummary = flags.emitModels ? ` \xB7 ${filteredModelEntries.length} models \xB7 ${Object.keys(enumEntries).length} enums` : "";
1579
1650
  console.log(`Summary: ${groupCount} groups \xB7 ${collected.length} entries \xB7 ${collected.length - missingValidators} validators bound \xB7 ${missingValidators} missing \xB7 ${skippedGroups} skipped${modelSummary}`);
1580
1651
  if (flags.strict && missingValidators > 0) {
1581
1652
  console.error(`[strict] ${missingValidators} validator(s) missing \u2014 failing build.`);
@@ -1607,6 +1678,10 @@ function deriveModelNamespace(projectName) {
1607
1678
  const base = (projectName ?? "cli").replaceAll(/[^a-zA-Z0-9]+/g, "_");
1608
1679
  return `${base.toUpperCase()}_MODEL_MANIFEST`;
1609
1680
  }
1681
+ function deriveEnumNamespace(projectName) {
1682
+ const base = (projectName ?? "cli").replaceAll(/[^a-zA-Z0-9]+/g, "_");
1683
+ return `${base.toUpperCase()}_ENUM_MANIFEST`;
1684
+ }
1610
1685
  function parseFlags(argv) {
1611
1686
  let only;
1612
1687
  let strict = false;
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-firebase-api-manifest",
3
- "version": "13.16.0",
3
+ "version": "13.17.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.16.0",
11
- "@dereekb/util": "13.16.0",
10
+ "@dereekb/dbx-cli": "13.17.0",
11
+ "@dereekb/util": "13.17.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.16.0",
8
+ version: "13.17.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.16.0"
15
+ "@dereekb/dbx-cli": "13.17.0"
16
16
  }
17
17
  };
18
18
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-firestore-indexes",
3
- "version": "13.16.0",
3
+ "version": "13.17.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.16.0"
10
+ "@dereekb/dbx-cli": "13.17.0"
11
11
  }
12
12
  }
@@ -1022,11 +1022,13 @@ function renderMcpManifest(input, now = /* @__PURE__ */ new Date()) {
1022
1022
  registerToolEntry({ entry, segments, nameCounts, tools, seenNames, warnings, errors });
1023
1023
  }
1024
1024
  const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
1025
+ const enums = input.enumManifest != null && Object.keys(input.enumManifest).length > 0 ? input.enumManifest : void 0;
1025
1026
  const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
1026
1027
  const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
1027
1028
  const manifest = {
1028
1029
  ...base,
1029
1030
  ...models == null ? {} : { models },
1031
+ ...enums == null ? {} : { enums },
1030
1032
  ...auth == null ? {} : { auth }
1031
1033
  };
1032
1034
  return { manifest, warnings, errors };
@@ -1312,8 +1314,9 @@ Expected output of \`nx run <cli>:generate-api-manifest\`.`);
1312
1314
  writeFileSync(tmpPath, serialized);
1313
1315
  renameSync(tmpPath, outputPath);
1314
1316
  const modelCount = manifest.models?.length ?? 0;
1317
+ const enumCount = manifest.enums == null ? 0 : Object.keys(manifest.enums).length;
1315
1318
  const authCount = manifest.auth?.claims.length ?? 0;
1316
- console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputPath)} \u2014 ${Object.keys(manifest.tools).length} tools, ${modelCount} models, ${authCount} auth claims`);
1319
+ console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputPath)} \u2014 ${Object.keys(manifest.tools).length} tools, ${modelCount} models, ${enumCount} enums, ${authCount} auth claims`);
1317
1320
  }
1318
1321
  async function maybeLoadAuth(flags) {
1319
1322
  if (flags.app == null || flags.claimsInputs.length === 0) {
@@ -1342,7 +1345,10 @@ async function loadManifest(path) {
1342
1345
  const namedModel = Object.entries(loaded).find(([key]) => key.endsWith("_MODEL_MANIFEST"));
1343
1346
  const modelManifestValue = namedModel?.[1];
1344
1347
  const modelManifest = Array.isArray(modelManifestValue) ? modelManifestValue : void 0;
1345
- return { apiManifest, modelManifest };
1348
+ const namedEnum = Object.entries(loaded).find(([key]) => key.endsWith("_ENUM_MANIFEST"));
1349
+ const enumManifestValue = namedEnum?.[1];
1350
+ const enumManifest = enumManifestValue != null && typeof enumManifestValue === "object" && !Array.isArray(enumManifestValue) ? enumManifestValue : void 0;
1351
+ return { apiManifest, modelManifest, enumManifest };
1346
1352
  }
1347
1353
  function loadTsconfigPathAliases() {
1348
1354
  const tsconfigPath = resolve(WORKSPACE_ROOT, "tsconfig.base.json");