@dereekb/dbx-cli 13.37.0 → 13.39.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 (106) hide show
  1. package/eslint/package.json +8 -8
  2. package/firebase-api-manifest/main.js +191 -166
  3. package/firebase-api-manifest/package.json +3 -3
  4. package/firestore-query-manifest/main.js +2063 -0
  5. package/firestore-query-manifest/package.json +13 -0
  6. package/firestore-rules/src/firestore-rules-scan.d.ts +89 -0
  7. package/firestore-rules/src/index.d.ts +1 -0
  8. package/generate-firestore-indexes/main.js +2 -2
  9. package/generate-firestore-indexes/package.json +2 -2
  10. package/generate-mcp-manifest/main.js +1 -0
  11. package/generate-mcp-manifest/package.json +3 -3
  12. package/generate-route-manifest/package.json +2 -2
  13. package/index.esm.js +12615 -6863
  14. package/lint-cache/package.json +2 -2
  15. package/manifest-extract/index.esm.js +4 -1
  16. package/manifest-extract/package.json +7 -7
  17. package/manifest-extract/src/lib/types.d.ts +6 -0
  18. package/model-test/LICENSE +21 -0
  19. package/model-test/index.d.ts +1 -0
  20. package/model-test/index.esm.js +5984 -0
  21. package/model-test/package.json +21 -0
  22. package/model-test/src/index.d.ts +41 -0
  23. package/model-test/src/lib/fixture/archetype.d.ts +33 -0
  24. package/model-test/src/lib/fixture/extract.d.ts +37 -0
  25. package/model-test/src/lib/fixture/format.json.d.ts +24 -0
  26. package/model-test/src/lib/fixture/format.markdown.d.ts +23 -0
  27. package/model-test/src/lib/fixture/forward.d.ts +48 -0
  28. package/model-test/src/lib/fixture/framework-fixtures.d.ts +70 -0
  29. package/model-test/src/lib/fixture/inspect.d.ts +22 -0
  30. package/model-test/src/lib/fixture/scaffold.d.ts +74 -0
  31. package/model-test/src/lib/fixture/types.d.ts +175 -0
  32. package/model-test/src/lib/test/discover.d.ts +81 -0
  33. package/model-test/src/lib/test/extract.d.ts +49 -0
  34. package/model-test/src/lib/test/format.hotspots.d.ts +23 -0
  35. package/model-test/src/lib/test/format.json.d.ts +25 -0
  36. package/model-test/src/lib/test/format.list-app.d.ts +30 -0
  37. package/model-test/src/lib/test/format.markdown.d.ts +25 -0
  38. package/model-test/src/lib/test/hotspots.d.ts +98 -0
  39. package/model-test/src/lib/test/inspect.d.ts +32 -0
  40. package/model-test/src/lib/test/search.d.ts +34 -0
  41. package/model-test/src/lib/test/types.d.ts +179 -0
  42. package/package.json +23 -22
  43. package/route/package.json +12 -12
  44. package/src/lib/api/firestore-session.client.d.ts +54 -0
  45. package/src/lib/api/get-args.helper.d.ts +4 -1
  46. package/src/lib/api/get-many.command.d.ts +8 -3
  47. package/src/lib/api/get.command.d.ts +13 -5
  48. package/src/lib/api/index.d.ts +1 -0
  49. package/src/lib/config/env.d.ts +106 -0
  50. package/src/lib/config/firestore-session.cache.d.ts +86 -0
  51. package/src/lib/config/index.d.ts +1 -0
  52. package/src/lib/config/paths.d.ts +3 -1
  53. package/src/lib/context/cli.context.d.ts +87 -3
  54. package/src/lib/doctor/firestore-session.check.d.ts +113 -0
  55. package/src/lib/doctor/index.d.ts +1 -0
  56. package/src/lib/firestore/firestore-get.command.d.ts +28 -0
  57. package/src/lib/firestore/firestore-queries.command.d.ts +26 -0
  58. package/src/lib/firestore/firestore-query.command.d.ts +25 -0
  59. package/src/lib/firestore/firestore.accessor.d.ts +85 -0
  60. package/src/lib/firestore/firestore.collection.d.ts +44 -0
  61. package/src/lib/firestore/firestore.error.d.ts +14 -0
  62. package/src/lib/firestore/firestore.models.d.ts +223 -0
  63. package/src/lib/firestore/firestore.query-params.d.ts +34 -0
  64. package/src/lib/firestore/firestore.query.d.ts +79 -0
  65. package/src/lib/firestore/firestore.read.d.ts +180 -0
  66. package/src/lib/firestore/firestore.session.d.ts +97 -0
  67. package/src/lib/firestore/index.d.ts +13 -0
  68. package/src/lib/firestore/query-info-utils.d.ts +48 -0
  69. package/src/lib/firestore/query-registry.d.ts +32 -0
  70. package/src/lib/index.d.ts +3 -0
  71. package/src/lib/manifest/types.d.ts +117 -0
  72. package/src/lib/mcp-scan/manifest/dbx-docs-ui-examples-schema.d.ts +2 -2
  73. package/src/lib/mcp-scan/manifest/model-snapshot-fields-schema.d.ts +2 -2
  74. package/src/lib/mcp-scan/manifest/pipes-schema.d.ts +2 -2
  75. package/src/lib/mcp-scan/manifest/ui-components-schema.d.ts +2 -2
  76. package/src/lib/mcp-scan/manifest/utils-schema.d.ts +2 -2
  77. package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-extract.d.ts +1 -1
  78. package/src/lib/mcp-scan/scan/extract-models/types.d.ts +5 -0
  79. package/src/lib/mcp-scan/scan/ui-components-extract.d.ts +1 -1
  80. package/src/lib/middleware/auth.middleware.d.ts +6 -0
  81. package/src/lib/runner/run.d.ts +28 -1
  82. package/src/lib/scan-helpers/emit-generated-ts.d.ts +76 -0
  83. package/src/lib/scan-helpers/exported-from-package.d.ts +29 -0
  84. package/src/lib/util/index.d.ts +1 -0
  85. package/src/lib/util/output.d.ts +10 -0
  86. package/src/lib/util/stdin.d.ts +39 -2
  87. package/src/lib/util/table.d.ts +40 -0
  88. package/test/index.esm.js +4 -2
  89. package/test/package.json +14 -14
  90. package/test/src/lib/cli-test.d.ts +12 -2
  91. package/validate/LICENSE +21 -0
  92. package/validate/index.js +16641 -0
  93. package/validate/package.json +11 -0
  94. package/eslint/index.cjs.default.js +0 -1
  95. package/eslint/index.cjs.js +0 -1112
  96. package/eslint/index.cjs.mjs +0 -2
  97. package/index.cjs.js +0 -59813
  98. package/manifest-extract/index.cjs.default.js +0 -1
  99. package/manifest-extract/index.cjs.js +0 -1592
  100. package/manifest-extract/index.cjs.mjs +0 -2
  101. package/route/index.cjs.default.js +0 -1
  102. package/route/index.cjs.js +0 -18
  103. package/route/index.cjs.mjs +0 -2
  104. package/test/index.cjs.default.js +0 -1
  105. package/test/index.cjs.js +0 -381
  106. package/test/index.cjs.mjs +0 -2
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/eslint",
3
- "version": "13.37.0",
3
+ "version": "13.39.0",
4
+ "type": "module",
4
5
  "peerDependencies": {
5
- "@dereekb/dbx-cli": "13.37.0",
6
- "@dereekb/util": "13.37.0",
6
+ "@dereekb/dbx-cli": "13.39.0",
7
+ "@dereekb/util": "13.39.0",
7
8
  "@typescript-eslint/utils": "8.59.3",
8
9
  "typescript": "5.9.3"
9
10
  },
@@ -14,13 +15,12 @@
14
15
  "exports": {
15
16
  "./package.json": "./package.json",
16
17
  ".": {
17
- "module": "./index.esm.js",
18
18
  "types": "./index.d.ts",
19
- "import": "./index.cjs.mjs",
20
- "default": "./index.cjs.js"
19
+ "import": "./index.esm.js",
20
+ "default": "./index.esm.js"
21
21
  }
22
22
  },
23
23
  "module": "./index.esm.js",
24
- "main": "./index.cjs.js",
24
+ "main": "./index.esm.js",
25
25
  "types": "./index.d.ts"
26
- }
26
+ }
@@ -3,15 +3,56 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
 
5
5
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/main.ts
6
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "node:fs";
7
- import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve3 } from "node:path";
6
+ import { existsSync as existsSync4 } from "node:fs";
7
+ import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve3 } from "node:path";
8
+
9
+ // packages/util/src/lib/string/sort.ts
10
+ var compareStrings = (a, b) => a.localeCompare(b);
11
+
12
+ // packages/dbx-cli/src/lib/scan-helpers/emit-generated-ts.ts
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { dirname } from "node:path";
15
+ import { format, resolveConfig } from "prettier";
16
+ // @__NO_SIDE_EFFECTS__
17
+ function renderGroupedImportLines(imports) {
18
+ const importsByPackage = /* @__PURE__ */ new Map();
19
+ for (const { packageName, identifier } of imports) {
20
+ if (!packageName || !identifier) continue;
21
+ const set = importsByPackage.get(packageName) ?? /* @__PURE__ */ new Set();
22
+ set.add(identifier);
23
+ importsByPackage.set(packageName, set);
24
+ }
25
+ return [...importsByPackage.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([pkg, names]) => {
26
+ const sortedNames = [...names].sort(compareStrings).join(", ");
27
+ return `import { ${sortedNames} } from '${pkg}';`;
28
+ });
29
+ }
30
+ async function formatGeneratedTs(source, outputFile) {
31
+ const config = await resolveConfig(outputFile);
32
+ return format(source, { ...config, filepath: outputFile });
33
+ }
34
+ function writeGeneratedTsFile(input) {
35
+ const { outputFile, contents } = input;
36
+ const outputDir = dirname(outputFile);
37
+ let result;
38
+ if (!existsSync(outputDir)) {
39
+ mkdirSync(outputDir, { recursive: true });
40
+ }
41
+ if (existsSync(outputFile) && readFileSync(outputFile, "utf8") === contents) {
42
+ result = "unchanged";
43
+ } else {
44
+ writeFileSync(outputFile, contents);
45
+ result = "wrote";
46
+ }
47
+ return result;
48
+ }
8
49
 
9
50
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/parse-functions.ts
10
- import { readFileSync } from "node:fs";
51
+ import { readFileSync as readFileSync2 } from "node:fs";
11
52
  import { Node, Project } from "ts-morph";
12
53
  function parseFunctionsConfig(functionsTsPath) {
13
54
  const project = new Project({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
14
- const text = readFileSync(functionsTsPath, "utf8");
55
+ const text = readFileSync2(functionsTsPath, "utf8");
15
56
  const sourceFile = project.createSourceFile("functions.ts", text, { overwrite: true });
16
57
  const importIdentToModule = collectImportSpecifierMap(sourceFile);
17
58
  const result = [];
@@ -69,8 +110,8 @@ function collectImportSpecifierMap(sourceFile) {
69
110
  }
70
111
 
71
112
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/resolve-package.ts
72
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
73
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
113
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
114
+ import { dirname as dirname2, isAbsolute, join, relative, resolve } from "node:path";
74
115
  var _pathsCache;
75
116
  function loadTsconfigPaths(workspaceRoot) {
76
117
  let result;
@@ -78,7 +119,7 @@ function loadTsconfigPaths(workspaceRoot) {
78
119
  result = _pathsCache;
79
120
  } else {
80
121
  const tsconfigPath = join(workspaceRoot, "tsconfig.base.json");
81
- const raw = readFileSync2(tsconfigPath, "utf8");
122
+ const raw = readFileSync3(tsconfigPath, "utf8");
82
123
  const cleaned = stripJsonComments(raw);
83
124
  const tsconfig = JSON.parse(cleaned);
84
125
  const paths = tsconfig.compilerOptions?.paths ?? {};
@@ -97,7 +138,7 @@ function resolveModuleToPackage(input) {
97
138
  const { workspaceRoot, importingFile, moduleSpecifier } = input;
98
139
  let result;
99
140
  if (moduleSpecifier.startsWith(".")) {
100
- const importingDir = dirname(importingFile);
141
+ const importingDir = dirname2(importingFile);
101
142
  const candidatePath = resolve(importingDir, moduleSpecifier);
102
143
  result = locatePackageForPath(workspaceRoot, candidatePath);
103
144
  } else {
@@ -113,38 +154,38 @@ function resolveModuleToPackage(input) {
113
154
  }
114
155
  function locatePackageInNodeModules(input) {
115
156
  const { importingFile, moduleSpecifier } = input;
116
- let current = dirname(importingFile);
157
+ let current = dirname2(importingFile);
117
158
  let result;
118
- while (!result && current && current !== dirname(current)) {
159
+ while (!result && current && current !== dirname2(current)) {
119
160
  const candidateRoot = join(current, "node_modules", moduleSpecifier);
120
161
  const pkgJson = join(candidateRoot, "package.json");
121
- if (existsSync(pkgJson)) {
162
+ if (existsSync2(pkgJson)) {
122
163
  try {
123
- const pkg = JSON.parse(readFileSync2(pkgJson, "utf8"));
164
+ const pkg = JSON.parse(readFileSync3(pkgJson, "utf8"));
124
165
  if (pkg.name) {
125
166
  result = { packageName: pkg.name, packageRoot: candidateRoot };
126
167
  }
127
168
  } catch {
128
169
  }
129
170
  }
130
- if (!result) current = dirname(current);
171
+ if (!result) current = dirname2(current);
131
172
  }
132
173
  return result;
133
174
  }
134
175
  function locatePackageForPath(workspaceRoot, startPath) {
135
176
  let current = startPath;
136
177
  let result;
137
- while (!result && current && current !== workspaceRoot && current !== dirname(current)) {
138
- if (existsSync(join(current, "package.json"))) {
178
+ while (!result && current && current !== workspaceRoot && current !== dirname2(current)) {
179
+ if (existsSync2(join(current, "package.json"))) {
139
180
  try {
140
- const pkg = JSON.parse(readFileSync2(join(current, "package.json"), "utf8"));
181
+ const pkg = JSON.parse(readFileSync3(join(current, "package.json"), "utf8"));
141
182
  if (pkg.name) {
142
183
  result = { packageName: pkg.name, packageRoot: current };
143
184
  }
144
185
  } catch {
145
186
  }
146
187
  }
147
- if (!result) current = dirname(current);
188
+ if (!result) current = dirname2(current);
148
189
  }
149
190
  return result;
150
191
  }
@@ -156,8 +197,8 @@ function stripJsonComments(text) {
156
197
  }
157
198
 
158
199
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-api-files.ts
159
- import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
160
- import { join as join2 } from "node:path";
200
+ import { readdirSync, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
201
+ import { join as join3 } from "node:path";
161
202
 
162
203
  // packages/dbx-cli/manifest-extract/src/lib/extract-crud.ts
163
204
  import { Node as Node2, Project as Project2 } from "ts-morph";
@@ -469,9 +510,6 @@ function readJsDocSummary(node) {
469
510
  return result;
470
511
  }
471
512
 
472
- // packages/util/src/lib/string/sort.ts
473
- var compareStrings = (a, b) => a.localeCompare(b);
474
-
475
513
  // packages/dbx-cli/src/lib/scan-helpers/firestore-model-extract-utils.ts
476
514
  import { Node as Node3 } from "ts-morph";
477
515
  var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
@@ -548,6 +586,114 @@ function identifierName(node) {
548
586
  return result;
549
587
  }
550
588
 
589
+ // packages/dbx-cli/src/lib/scan-helpers/exported-from-package.ts
590
+ import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "node:fs";
591
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
592
+ function isExportedFromPackage(input) {
593
+ const { packageRoot, identifier } = input;
594
+ const indexPath = locateBarrelEntry(packageRoot);
595
+ return indexPath ? findIdentifierInBarrelChain(indexPath, identifier, /* @__PURE__ */ new Set()) : false;
596
+ }
597
+ function locateBarrelEntry(packageRoot) {
598
+ const candidates = [join2(packageRoot, "src", "index.ts"), join2(packageRoot, "src", "index.d.ts"), join2(packageRoot, "index.d.ts"), join2(packageRoot, "index.ts")];
599
+ return candidates.find((candidate) => existsSync3(candidate));
600
+ }
601
+ var EXPORT_DECL_PATTERNS = [/export\s+(?:declare\s+)?const\s+IDENT\b/, /export\s+(?:declare\s+)?function\s+IDENT\b/, /export\s*\{[^}]*\bIDENT\b[^}]*\}/];
602
+ function findIdentifierInBarrelChain(filePath, identifier, visited) {
603
+ let result = false;
604
+ if (!visited.has(filePath)) {
605
+ visited.add(filePath);
606
+ const text = readFileTextSafe(filePath);
607
+ if (text !== void 0) {
608
+ result = barrelTextDeclaresIdentifier(text, identifier) || reExportChainHasIdentifier({ fromFile: filePath, text, identifier, visited });
609
+ }
610
+ }
611
+ return result;
612
+ }
613
+ function readFileTextSafe(filePath) {
614
+ let text;
615
+ try {
616
+ text = readFileSync4(filePath, "utf8");
617
+ } catch {
618
+ text = void 0;
619
+ }
620
+ return text;
621
+ }
622
+ function barrelTextDeclaresIdentifier(text, identifier) {
623
+ let result = false;
624
+ for (const pattern of EXPORT_DECL_PATTERNS) {
625
+ const re = new RegExp(pattern.source.replace("IDENT", escapeRegExp(identifier)));
626
+ if (re.test(text)) {
627
+ result = true;
628
+ break;
629
+ }
630
+ }
631
+ return result;
632
+ }
633
+ function reExportChainHasIdentifier(input) {
634
+ const { fromFile, text, identifier, visited } = input;
635
+ let result = false;
636
+ const dir = dirname3(fromFile);
637
+ for (const reExportTarget of collectReExportTargets(text)) {
638
+ const resolved = resolveReExport(dir, reExportTarget);
639
+ if (resolved && findIdentifierInBarrelChain(resolved, identifier, visited)) {
640
+ result = true;
641
+ break;
642
+ }
643
+ }
644
+ return result;
645
+ }
646
+ function collectReExportTargets(text) {
647
+ const out = [];
648
+ const re = /export\s*(?:\*|\{[^}]*\})\s*from\s*['"]([^'"]+)['"]/g;
649
+ let match;
650
+ while ((match = re.exec(text)) !== null) {
651
+ out.push(match[1]);
652
+ }
653
+ return out;
654
+ }
655
+ function resolveReExport(fromDir, target) {
656
+ let result;
657
+ if (target.startsWith(".")) {
658
+ const candidate = isAbsolute2(target) ? target : resolve2(fromDir, target);
659
+ for (const ext of [".ts", ".mts", ".d.ts", "/index.ts", "/index.mts", "/index.d.ts"]) {
660
+ const probe = hasTsModuleExtension(candidate) ? candidate : candidate + ext;
661
+ const resolved = resolveExistingTsPath(probe);
662
+ if (resolved) {
663
+ result = resolved;
664
+ break;
665
+ }
666
+ }
667
+ }
668
+ return result;
669
+ }
670
+ function hasTsModuleExtension(value) {
671
+ return value.endsWith(".ts") || value.endsWith(".mts");
672
+ }
673
+ function resolveExistingTsPath(probe) {
674
+ let result;
675
+ if (existsSync3(probe)) {
676
+ const stat = statSync(probe);
677
+ if (stat.isFile()) {
678
+ result = probe;
679
+ } else if (stat.isDirectory()) {
680
+ const sourceIndex = join2(probe, "index.ts");
681
+ if (existsSync3(sourceIndex)) {
682
+ result = sourceIndex;
683
+ } else {
684
+ const declarationIndex = join2(probe, "index.d.ts");
685
+ if (existsSync3(declarationIndex)) {
686
+ result = declarationIndex;
687
+ }
688
+ }
689
+ }
690
+ }
691
+ return result;
692
+ }
693
+ function escapeRegExp(value) {
694
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
695
+ }
696
+
551
697
  // packages/dbx-cli/manifest-extract/src/lib/extract-models.ts
552
698
  import { Node as Node4, Project as Project3 } from "ts-morph";
553
699
  var READ_LEVEL_VALUES = /* @__PURE__ */ new Set(["system", "owner", "admin-only", "permissions"]);
@@ -602,6 +748,7 @@ function buildInterface(decl) {
602
748
  const jsDocs = decl.getJsDocs();
603
749
  const hasDbxModelTag = jsDocsHaveTag(jsDocs, "dbxModel");
604
750
  const dbxModelRead = readDbxModelReadTag(jsDocs);
751
+ const dbxModelServerOnly = jsDocsHaveTag(jsDocs, "dbxModelServerOnly");
605
752
  const mcpToolNameSegment = readMcpToolNameSegmentTag(jsDocs);
606
753
  const extendsNames = decl.getExtends().map(resolveExtendsName);
607
754
  const props = [];
@@ -627,6 +774,7 @@ function buildInterface(decl) {
627
774
  extendsNames,
628
775
  props,
629
776
  ...dbxModelRead === void 0 ? {} : { dbxModelRead },
777
+ ...dbxModelServerOnly ? { dbxModelServerOnly: true } : {},
630
778
  ...mcpToolNameSegment === void 0 ? {} : { mcpToolNameSegment }
631
779
  };
632
780
  }
@@ -934,12 +1082,12 @@ function firstParagraph(text) {
934
1082
 
935
1083
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-api-files.ts
936
1084
  function findApiFiles(packageRoot) {
937
- const libRoot = join2(packageRoot, "src", "lib");
1085
+ const libRoot = join3(packageRoot, "src", "lib");
938
1086
  const out = [];
939
1087
  if (safeIsDirectory(libRoot)) {
940
1088
  const seenClassNames = /* @__PURE__ */ new Set();
941
1089
  for (const file of walkApiFiles(libRoot)) {
942
- const text = readFileSync3(file, "utf8");
1090
+ const text = readFileSync5(file, "utf8");
943
1091
  const extraction = extractCrudEntries({ name: file, text });
944
1092
  if (!extraction.functionsClassName) continue;
945
1093
  if (seenClassNames.has(extraction.functionsClassName)) continue;
@@ -952,8 +1100,8 @@ function findApiFiles(packageRoot) {
952
1100
  function* walkApiFiles(dir) {
953
1101
  for (const entry of readdirSync(dir).sort()) {
954
1102
  if (entry === "node_modules" || entry === "dist") continue;
955
- const p = join2(dir, entry);
956
- const stat = statSync(p);
1103
+ const p = join3(dir, entry);
1104
+ const stat = statSync2(p);
957
1105
  if (stat.isDirectory()) {
958
1106
  yield* walkApiFiles(p);
959
1107
  } else if (isApiFile(entry)) {
@@ -968,7 +1116,7 @@ function isApiFile(name) {
968
1116
  function safeIsDirectory(p) {
969
1117
  let result;
970
1118
  try {
971
- result = statSync(p).isDirectory();
1119
+ result = statSync2(p).isDirectory();
972
1120
  } catch {
973
1121
  result = false;
974
1122
  }
@@ -976,14 +1124,14 @@ function safeIsDirectory(p) {
976
1124
  }
977
1125
 
978
1126
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-model-files.ts
979
- import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
980
- import { join as join3 } from "node:path";
1127
+ import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
1128
+ import { join as join4 } from "node:path";
981
1129
  function findModelFiles(packageRoot) {
982
- const libRoot = join3(packageRoot, "src", "lib");
1130
+ const libRoot = join4(packageRoot, "src", "lib");
983
1131
  const out = [];
984
1132
  if (safeIsDirectory2(libRoot)) {
985
1133
  for (const filePath of walkSourceFiles(libRoot)) {
986
- const text = readFileSync4(filePath, "utf8");
1134
+ const text = readFileSync6(filePath, "utf8");
987
1135
  if (!textHasModelMarker(text)) continue;
988
1136
  const extraction = extractModelsFromSource({ name: filePath, text });
989
1137
  if (extraction.identities.length === 0 && extraction.modelGroups.length === 0 && extraction.converters.length === 0 && extraction.serviceFactories.length === 0) continue;
@@ -998,8 +1146,8 @@ function textHasModelMarker(text) {
998
1146
  function* walkSourceFiles(dir) {
999
1147
  for (const entry of readdirSync2(dir).sort()) {
1000
1148
  if (entry === "node_modules" || entry === "dist") continue;
1001
- const p = join3(dir, entry);
1002
- const stat = statSync2(p);
1149
+ const p = join4(dir, entry);
1150
+ const stat = statSync3(p);
1003
1151
  if (stat.isDirectory()) {
1004
1152
  yield* walkSourceFiles(p);
1005
1153
  } else if (isCandidateSourceFile(entry)) {
@@ -1013,7 +1161,7 @@ function isCandidateSourceFile(name) {
1013
1161
  function safeIsDirectory2(p) {
1014
1162
  let result;
1015
1163
  try {
1016
- result = statSync2(p).isDirectory();
1164
+ result = statSync3(p).isDirectory();
1017
1165
  } catch {
1018
1166
  result = false;
1019
1167
  }
@@ -1162,6 +1310,7 @@ function buildManifestEntry(input) {
1162
1310
  fields,
1163
1311
  ...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
1164
1312
  ...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
1313
+ ...iface.dbxModelServerOnly ? { serverOnly: true } : {},
1165
1314
  ...serviceFactory ? { serviceFactory } : {}
1166
1315
  };
1167
1316
  }
@@ -1298,131 +1447,19 @@ function capitalize(s) {
1298
1447
  }
1299
1448
 
1300
1449
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/bind-validators.ts
1301
- import { existsSync as existsSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
1302
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
1303
1450
  function deriveValidatorName(paramsTypeName) {
1304
1451
  return paramsTypeName ? paramsTypeName.charAt(0).toLowerCase() + paramsTypeName.slice(1) + "Type" : "";
1305
1452
  }
1306
- function isExportedFromPackage(input) {
1307
- const { packageRoot, identifier } = input;
1308
- const indexPath = locateBarrelEntry(packageRoot);
1309
- return indexPath ? findIdentifierInBarrelChain(indexPath, identifier, /* @__PURE__ */ new Set()) : false;
1310
- }
1311
- function locateBarrelEntry(packageRoot) {
1312
- const candidates = [join4(packageRoot, "src", "index.ts"), join4(packageRoot, "src", "index.d.ts"), join4(packageRoot, "index.d.ts"), join4(packageRoot, "index.ts")];
1313
- return candidates.find((candidate) => existsSync2(candidate));
1314
- }
1315
- var EXPORT_DECL_PATTERNS = [/export\s+(?:declare\s+)?const\s+IDENT\b/, /export\s+(?:declare\s+)?function\s+IDENT\b/, /export\s*\{[^}]*\bIDENT\b[^}]*\}/];
1316
- function findIdentifierInBarrelChain(filePath, identifier, visited) {
1317
- let result = false;
1318
- if (!visited.has(filePath)) {
1319
- visited.add(filePath);
1320
- const text = readFileTextSafe(filePath);
1321
- if (text !== void 0) {
1322
- result = barrelTextDeclaresIdentifier(text, identifier) || reExportChainHasIdentifier({ fromFile: filePath, text, identifier, visited });
1323
- }
1324
- }
1325
- return result;
1326
- }
1327
- function readFileTextSafe(filePath) {
1328
- let text;
1329
- try {
1330
- text = readFileSync5(filePath, "utf8");
1331
- } catch {
1332
- text = void 0;
1333
- }
1334
- return text;
1335
- }
1336
- function barrelTextDeclaresIdentifier(text, identifier) {
1337
- let result = false;
1338
- for (const pattern of EXPORT_DECL_PATTERNS) {
1339
- const re = new RegExp(pattern.source.replace("IDENT", escapeRegExp(identifier)));
1340
- if (re.test(text)) {
1341
- result = true;
1342
- break;
1343
- }
1344
- }
1345
- return result;
1346
- }
1347
- function reExportChainHasIdentifier(input) {
1348
- const { fromFile, text, identifier, visited } = input;
1349
- let result = false;
1350
- const dir = dirname2(fromFile);
1351
- for (const reExportTarget of collectReExportTargets(text)) {
1352
- const resolved = resolveReExport(dir, reExportTarget);
1353
- if (resolved && findIdentifierInBarrelChain(resolved, identifier, visited)) {
1354
- result = true;
1355
- break;
1356
- }
1357
- }
1358
- return result;
1359
- }
1360
- function collectReExportTargets(text) {
1361
- const out = [];
1362
- const re = /export\s*(?:\*|\{[^}]*\})\s*from\s*['"]([^'"]+)['"]/g;
1363
- let match;
1364
- while ((match = re.exec(text)) !== null) {
1365
- out.push(match[1]);
1366
- }
1367
- return out;
1368
- }
1369
- function resolveReExport(fromDir, target) {
1370
- let result;
1371
- if (target.startsWith(".")) {
1372
- const candidate = isAbsolute2(target) ? target : resolve2(fromDir, target);
1373
- for (const ext of [".ts", ".mts", ".d.ts", "/index.ts", "/index.mts", "/index.d.ts"]) {
1374
- const probe = hasTsModuleExtension(candidate) ? candidate : candidate + ext;
1375
- const resolved = resolveExistingTsPath(probe);
1376
- if (resolved) {
1377
- result = resolved;
1378
- break;
1379
- }
1380
- }
1381
- }
1382
- return result;
1383
- }
1384
- function hasTsModuleExtension(value) {
1385
- return value.endsWith(".ts") || value.endsWith(".mts");
1386
- }
1387
- function resolveExistingTsPath(probe) {
1388
- let result;
1389
- if (existsSync2(probe)) {
1390
- const stat = statSync3(probe);
1391
- if (stat.isFile()) {
1392
- result = probe;
1393
- } else if (stat.isDirectory()) {
1394
- const sourceIndex = join4(probe, "index.ts");
1395
- if (existsSync2(sourceIndex)) {
1396
- result = sourceIndex;
1397
- } else {
1398
- const declarationIndex = join4(probe, "index.d.ts");
1399
- if (existsSync2(declarationIndex)) {
1400
- result = declarationIndex;
1401
- }
1402
- }
1403
- }
1404
- }
1405
- return result;
1406
- }
1407
- function escapeRegExp(value) {
1408
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
1409
- }
1410
1453
 
1411
1454
  // packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/emit.ts
1412
- import { format, resolveConfig } from "prettier";
1413
1455
  async function renderManifest(input) {
1414
1456
  const { outputFile, entries, projectName, namespace, modelEntries, modelNamespace, enumEntries, enumNamespace, emitConverters = false } = input;
1415
- const importsByPackage = /* @__PURE__ */ new Map();
1457
+ const validatorImports = [];
1416
1458
  for (const entry of entries) {
1417
1459
  if (!entry.packageName || !entry.validatorName) continue;
1418
- const set = importsByPackage.get(entry.packageName) ?? /* @__PURE__ */ new Set();
1419
- set.add(entry.validatorName);
1420
- importsByPackage.set(entry.packageName, set);
1460
+ validatorImports.push({ packageName: entry.packageName, identifier: entry.validatorName });
1421
1461
  }
1422
- const importLines = [...importsByPackage.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([pkg, names]) => {
1423
- const sortedNames = [...names].sort(compareStrings).join(", ");
1424
- return `import { ${sortedNames} } from '${pkg}';`;
1425
- });
1462
+ const importLines = renderGroupedImportLines(validatorImports);
1426
1463
  const entryLines = entries.map((e) => renderEntry(e));
1427
1464
  const emitModels = Boolean(modelEntries && modelEntries.length > 0 && modelNamespace);
1428
1465
  const emitEnums = Boolean(enumEntries && Object.keys(enumEntries).length > 0 && enumNamespace);
@@ -1449,7 +1486,7 @@ export const ${namespace}: CliApiManifest = [
1449
1486
  ${entryLines.join(",\n")}
1450
1487
  ];${modelSection}${enumSection}
1451
1488
  `;
1452
- return formatWithPrettier(source, outputFile);
1489
+ return formatGeneratedTs(source, outputFile);
1453
1490
  }
1454
1491
  function renderEntry({ entry, validatorName }) {
1455
1492
  const fields = [
@@ -1480,10 +1517,6 @@ function renderDocFields(fields) {
1480
1517
  });
1481
1518
  return `[${items.join(", ")}]`;
1482
1519
  }
1483
- async function formatWithPrettier(source, outputFile) {
1484
- const config = await resolveConfig(outputFile);
1485
- return format(source, { ...config, filepath: outputFile });
1486
- }
1487
1520
  function renderModelEntry(entry, emitConverters) {
1488
1521
  const fields = [
1489
1522
  `modelType: ${JSON.stringify(entry.modelType)}`,
@@ -1498,6 +1531,7 @@ function renderModelEntry(entry, emitConverters) {
1498
1531
  `fields: ${renderModelFields(entry.fields, emitConverters)}`,
1499
1532
  entry.mcpToolNameSegment ? `mcpToolNameSegment: ${JSON.stringify(entry.mcpToolNameSegment)}` : void 0,
1500
1533
  entry.read ? `read: ${JSON.stringify(entry.read)}` : void 0,
1534
+ entry.serverOnly ? "serverOnly: true" : void 0,
1501
1535
  entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0
1502
1536
  ];
1503
1537
  return ` { ${fields.filter(Boolean).join(", ")} }`;
@@ -1556,10 +1590,9 @@ async function main() {
1556
1590
  }
1557
1591
  const functionsConfigPath = resolveWorkspacePath(flags.functionsConfig);
1558
1592
  const outputFile = resolveWorkspacePath(flags.output);
1559
- const outputDir = dirname3(outputFile);
1560
1593
  const projectName = flags.project ?? "<cli>";
1561
1594
  const namespace = deriveNamespace(flags.project);
1562
- if (!existsSync3(functionsConfigPath)) {
1595
+ if (!existsSync4(functionsConfigPath)) {
1563
1596
  throw new Error(`functions-config file not found: ${functionsConfigPath}`);
1564
1597
  }
1565
1598
  const groups = parseFunctionsConfig(functionsConfigPath);
@@ -1637,14 +1670,9 @@ async function main() {
1637
1670
  const modelEntries = flags.emitModels ? assembleModels({ extractions: modelSources }) : [];
1638
1671
  const filteredModelEntries = flags.only ? modelEntries.filter((m) => flags.only?.has(m.modelType)) : modelEntries;
1639
1672
  const enumEntries = flags.emitModels ? collectModelEnums({ extractions: modelSources, models: filteredModelEntries }) : {};
1640
- ensureOutputDir(outputDir);
1641
1673
  const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace, modelEntries: filteredModelEntries, modelNamespace: deriveModelNamespace(flags.project), enumEntries, enumNamespace: deriveEnumNamespace(flags.project), emitConverters: flags.emitModelConverters });
1642
- if (existsSync3(outputFile) && readFileSync6(outputFile, "utf8") === formatted) {
1643
- console.log(`[unchanged] ${relative2(WORKSPACE_ROOT, outputFile)}`);
1644
- } else {
1645
- writeFileSync(outputFile, formatted);
1646
- console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputFile)}`);
1647
- }
1674
+ const outcome = writeGeneratedTsFile({ outputFile, contents: formatted });
1675
+ console.log(`[${outcome}] ${relative2(WORKSPACE_ROOT, outputFile)}`);
1648
1676
  const groupCount = packageCache.size === 0 ? 0 : new Set(collected.map((c) => c.entry.groupName)).size;
1649
1677
  const modelSummary = flags.emitModels ? ` \xB7 ${filteredModelEntries.length} models \xB7 ${Object.keys(enumEntries).length} enums` : "";
1650
1678
  console.log(`Summary: ${groupCount} groups \xB7 ${collected.length} entries \xB7 ${collected.length - missingValidators} validators bound \xB7 ${missingValidators} missing \xB7 ${skippedGroups} skipped${modelSummary}`);
@@ -1664,9 +1692,6 @@ function compareEntries(a, b) {
1664
1692
  }
1665
1693
  return result;
1666
1694
  }
1667
- function ensureOutputDir(outputDir) {
1668
- if (!existsSync3(outputDir)) mkdirSync(outputDir, { recursive: true });
1669
- }
1670
1695
  function resolveWorkspacePath(value) {
1671
1696
  return isAbsolute3(value) ? value : resolve3(WORKSPACE_ROOT, value);
1672
1697
  }
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-firebase-api-manifest",
3
- "version": "13.37.0",
3
+ "version": "13.39.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.37.0",
11
- "@dereekb/util": "13.37.0",
10
+ "@dereekb/dbx-cli": "13.39.0",
11
+ "@dereekb/util": "13.39.0",
12
12
  "prettier": "3.8.3"
13
13
  }
14
14
  }