@dereekb/dbx-cli 13.40.0 → 13.42.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 (35) hide show
  1. package/eslint/package.json +3 -3
  2. package/firebase-api-manifest/main.js +156 -18
  3. package/firebase-api-manifest/package.json +3 -3
  4. package/firestore-indexes/src/generate-firestore-indexes-cli.d.ts +12 -4
  5. package/firestore-indexes/src/model-firebase-index-analyze.d.ts +12 -2
  6. package/firestore-query-manifest/main.js +306 -12
  7. package/firestore-query-manifest/package.json +3 -3
  8. package/generate-firestore-indexes/main.js +118 -20
  9. package/generate-firestore-indexes/package.json +2 -2
  10. package/generate-mcp-manifest/main.js +5 -2
  11. package/generate-mcp-manifest/package.json +3 -3
  12. package/generate-route-manifest/main.js +1 -1
  13. package/generate-route-manifest/package.json +2 -2
  14. package/index.esm.js +1389 -933
  15. package/lint-cache/package.json +2 -2
  16. package/manifest-extract/index.esm.js +148 -2
  17. package/manifest-extract/package.json +2 -2
  18. package/manifest-extract/src/lib/extract-models.d.ts +4 -2
  19. package/manifest-extract/src/lib/types.d.ts +65 -0
  20. package/model-test/index.esm.js +4 -54
  21. package/model-test/package.json +2 -2
  22. package/package.json +6 -6
  23. package/route/package.json +7 -7
  24. package/src/lib/doctor/firestore-session.check.d.ts +23 -2
  25. package/src/lib/firestore/firestore.collection.d.ts +29 -0
  26. package/src/lib/firestore/firestore.query.d.ts +1 -1
  27. package/src/lib/firestore/index.d.ts +1 -0
  28. package/src/lib/firestore/query-info-utils.d.ts +6 -1
  29. package/src/lib/firestore/query-mode.d.ts +120 -0
  30. package/src/lib/manifest/types.d.ts +161 -2
  31. package/src/lib/mcp-scan/manifest/css-utilities-schema.d.ts +2 -2
  32. package/src/lib/mcp-scan/manifest/tokens-schema.d.ts +2 -2
  33. package/test/package.json +9 -9
  34. package/validate/index.js +12 -7
  35. package/validate/package.json +3 -3
@@ -9,15 +9,15 @@ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 }
9
9
  // packages/dbx-cli/firestore-query-manifest/package.json
10
10
  var package_default = {
11
11
  name: "@dereekb/dbx-cli-firestore-query-manifest",
12
- version: "13.40.0",
12
+ version: "13.42.0",
13
13
  private: true,
14
14
  type: "module",
15
15
  devDependencies: {
16
16
  eslint: "10.4.0"
17
17
  },
18
18
  peerDependencies: {
19
- "@dereekb/dbx-cli": "13.40.0",
20
- "@dereekb/util": "13.40.0"
19
+ "@dereekb/dbx-cli": "13.42.0",
20
+ "@dereekb/util": "13.42.0"
21
21
  }
22
22
  };
23
23
 
@@ -37,8 +37,8 @@ function renderGroupedImportLines(imports) {
37
37
  set.add(identifier);
38
38
  importsByPackage.set(packageName, set);
39
39
  }
40
- return [...importsByPackage.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([pkg, names]) => {
41
- const sortedNames = [...names].sort(compareStrings).join(", ");
40
+ return Array.from(importsByPackage.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([pkg, names]) => {
41
+ const sortedNames = Array.from(names).sort(compareStrings).join(", ");
42
42
  return `import { ${sortedNames} } from '${pkg}';`;
43
43
  });
44
44
  }
@@ -62,6 +62,241 @@ function writeGeneratedTsFile(input) {
62
62
  return result;
63
63
  }
64
64
 
65
+ // packages/dbx-cli/firestore-rules/src/firestore-rules-scan.ts
66
+ var RULES_ROOT_PREFIX = /^\/?databases\/\{[^}]*\}\/documents/;
67
+ function scanFirestoreRules(source) {
68
+ const text = stripComments(source);
69
+ const accumulated = /* @__PURE__ */ new Map();
70
+ walkMatchBlocks(text, (block) => {
71
+ const collection = collectionForPathSegments(block.segments);
72
+ if (collection == null) return;
73
+ const current = accumulated.get(collection) ?? (() => {
74
+ const created = { paths: /* @__PURE__ */ new Set(), get: "unmatched", list: "unmatched", collectionGroup: false };
75
+ accumulated.set(collection, created);
76
+ return created;
77
+ })();
78
+ current.paths.add("/" + block.segments.join("/"));
79
+ current.collectionGroup = current.collectionGroup || block.segments.some(isRecursiveWildcard);
80
+ for (const allow of block.allows) {
81
+ if (allow.ops.has("get") || allow.ops.has("read")) current.get = mergeAccess(current.get, allow.access);
82
+ if (allow.ops.has("list") || allow.ops.has("read")) current.list = mergeAccess(current.list, allow.access);
83
+ }
84
+ });
85
+ const collections = Array.from(accumulated.entries()).map(([collection, value]) => ({
86
+ collection,
87
+ paths: Array.from(value.paths).sort(),
88
+ get: value.get,
89
+ list: value.list,
90
+ collectionGroup: value.collectionGroup,
91
+ serverOnly: value.get !== "allowed" && value.list !== "allowed"
92
+ })).sort((a, b) => a.collection.localeCompare(b.collection));
93
+ return { collections };
94
+ }
95
+ // @__NO_SIDE_EFFECTS__
96
+ function firestoreRulesAccessForCollection(scan, collection) {
97
+ return scan.collections.find((x) => x.collection === collection) ?? { collection, paths: [], get: "unmatched", list: "unmatched", collectionGroup: false, serverOnly: true };
98
+ }
99
+ function mergeAccess(current, incoming) {
100
+ let result;
101
+ if (current === "allowed" || incoming === "allowed") {
102
+ result = "allowed";
103
+ } else if (current === "denied" || incoming === "denied") {
104
+ result = "denied";
105
+ } else {
106
+ result = "unmatched";
107
+ }
108
+ return result;
109
+ }
110
+ function collectionForPathSegments(segments) {
111
+ const candidate = segments.length >= 2 ? segments[segments.length - 2] : void 0;
112
+ return candidate != null && !isWildcard(candidate) ? candidate : void 0;
113
+ }
114
+ function isWildcard(segment) {
115
+ return segment.startsWith("{") && segment.endsWith("}");
116
+ }
117
+ function isRecursiveWildcard(segment) {
118
+ return isWildcard(segment) && segment.includes("=**");
119
+ }
120
+ function stripComments(source) {
121
+ let result = "";
122
+ let index = 0;
123
+ let quote;
124
+ while (index < source.length) {
125
+ const char = source[index];
126
+ const next = source[index + 1];
127
+ if (quote != null) {
128
+ result += char;
129
+ if (char === "\\" && index + 1 < source.length) {
130
+ result += next;
131
+ index += 2;
132
+ continue;
133
+ }
134
+ if (char === quote) quote = void 0;
135
+ index += 1;
136
+ } else if (char === "'" || char === '"') {
137
+ quote = char;
138
+ result += char;
139
+ index += 1;
140
+ } else if (char === "/" && next === "/") {
141
+ while (index < source.length && source[index] !== "\n") index += 1;
142
+ } else if (char === "/" && next === "*") {
143
+ index += 2;
144
+ while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) index += 1;
145
+ index += 2;
146
+ } else {
147
+ result += char;
148
+ index += 1;
149
+ }
150
+ }
151
+ return result;
152
+ }
153
+ function walkMatchBlocks(text, visit) {
154
+ const pathStack = [];
155
+ const braceFrames = [];
156
+ let index = 0;
157
+ while (index < text.length) {
158
+ const matchStart = findKeyword(text, index, "match");
159
+ const braceIndex = text.indexOf("{", index);
160
+ const closeIndex = text.indexOf("}", index);
161
+ const allowStart = findKeyword(text, index, "allow");
162
+ const next = Math.min(...[matchStart, braceIndex, closeIndex, allowStart].filter((x) => x >= 0).concat([text.length]));
163
+ if (next >= text.length) break;
164
+ if (next === matchStart) {
165
+ const parsed = parseMatchHeader(text, matchStart);
166
+ pathStack.push(parsed.segments);
167
+ braceFrames.push({ isMatch: true, allows: [] });
168
+ index = parsed.bodyStart;
169
+ } else if (next === allowStart) {
170
+ const parsed = parseAllow(text, allowStart);
171
+ const frame = braceFrames[braceFrames.length - 1];
172
+ if (frame?.isMatch && parsed) frame.allows.push(parsed.allow);
173
+ index = parsed ? parsed.end : allowStart + "allow".length;
174
+ } else if (next === braceIndex) {
175
+ braceFrames.push({ isMatch: false, allows: [] });
176
+ index = braceIndex + 1;
177
+ } else {
178
+ const frame = braceFrames.pop();
179
+ if (frame?.isMatch) {
180
+ const segments = stripRootPrefix(pathStack.flat());
181
+ visit({ segments, allows: frame.allows });
182
+ pathStack.pop();
183
+ }
184
+ index = closeIndex + 1;
185
+ }
186
+ }
187
+ }
188
+ function stripRootPrefix(segments) {
189
+ const joined = segments.join("/");
190
+ const stripped = joined.replace(RULES_ROOT_PREFIX, "");
191
+ return stripped.split("/").filter((x) => x.length > 0);
192
+ }
193
+ function parseMatchHeader(text, start) {
194
+ const braceIndex = text.indexOf("{", start + "match".length);
195
+ let cursor = start + "match".length;
196
+ let path = "";
197
+ while (cursor < text.length) {
198
+ const char = text[cursor];
199
+ if (char === "{") {
200
+ const close = text.indexOf("}", cursor);
201
+ const inner = text.slice(cursor, close + 1);
202
+ if (close > cursor && /^\{[A-Za-z_][\w]*(=\*\*)?\}$/.test(inner)) {
203
+ path += inner;
204
+ cursor = close + 1;
205
+ continue;
206
+ }
207
+ break;
208
+ }
209
+ if (char === "\n" && path.trim().length > 0) break;
210
+ path += char;
211
+ cursor += 1;
212
+ }
213
+ const bodyBrace = text.includes("{", cursor - 1) ? text.indexOf("{", cursor) : braceIndex;
214
+ const segments = path.trim().split("/").map((x) => x.trim()).filter((x) => x.length > 0);
215
+ return { segments, bodyStart: (bodyBrace >= 0 ? bodyBrace : cursor) + 1 };
216
+ }
217
+ function parseAllow(text, start) {
218
+ const colonIndex = text.indexOf(":", start);
219
+ const semiIndex = text.indexOf(";", start);
220
+ let result;
221
+ if (colonIndex >= 0 && semiIndex > colonIndex) {
222
+ const ops = new Set(
223
+ text.slice(start + "allow".length, colonIndex).split(",").map((x) => x.trim()).filter((x) => x.length > 0)
224
+ );
225
+ const condition = text.slice(colonIndex + 1, semiIndex).replace(/^\s*if\s*/, "").trim();
226
+ result = { allow: { ops, access: condition === "false" ? "denied" : "allowed" }, end: semiIndex + 1 };
227
+ }
228
+ return result;
229
+ }
230
+ function findKeyword(text, from, keyword) {
231
+ let index = text.indexOf(keyword, from);
232
+ while (index >= 0) {
233
+ const before = index === 0 ? " " : text[index - 1];
234
+ const after = text[index + keyword.length] ?? " ";
235
+ if (!/[\w$]/.test(before) && !/[\w$]/.test(after)) break;
236
+ index = text.indexOf(keyword, index + keyword.length);
237
+ }
238
+ return index;
239
+ }
240
+
241
+ // packages/dbx-cli/src/lib/firestore/query-mode.ts
242
+ // @__NO_SIDE_EFFECTS__
243
+ function cliFirestoreQueryModeForRules(input) {
244
+ const { scope, isNested, collectionGroup, list, parentPaths } = input;
245
+ const base = { list, collectionGroup, ...parentPaths && parentPaths.length > 0 ? { parentPaths } : {} };
246
+ let result;
247
+ if (list !== "allowed") {
248
+ const reason = list === "denied" ? "list-denied" : "list-unmatched";
249
+ result = { mode: "unavailable", rules: { ...base, reason } };
250
+ } else if (scope === "COLLECTION_GROUP" && !collectionGroup) {
251
+ result = { mode: isNested ? "parent-child" : "unavailable", rules: { ...base, reason: "no-collection-group-rule" } };
252
+ } else if (scope === "COLLECTION" && isNested) {
253
+ result = { mode: "parent-child", rules: { ...base, reason: "nested-collection-scope" } };
254
+ } else {
255
+ result = { mode: "model", rules: base };
256
+ }
257
+ return result;
258
+ }
259
+
260
+ // packages/dbx-cli/firestore-query-manifest/src/annotate-query-mode.ts
261
+ function annotateQueryEntryMode(input) {
262
+ const { entries, rulesSource } = input;
263
+ const scan = scanFirestoreRules(rulesSource);
264
+ let model = 0;
265
+ const unavailableSlugs = [];
266
+ const parentChildSlugs = [];
267
+ const annotated = entries.map((entry) => {
268
+ const rules = firestoreRulesAccessForCollection(scan, entry.collection);
269
+ const { mode, rules: entryRules } = cliFirestoreQueryModeForRules({
270
+ scope: entry.scope,
271
+ isNested: entry.isNested,
272
+ collectionGroup: rules.collectionGroup,
273
+ list: rules.list,
274
+ parentPaths: parentPathsForCollection(scan, entry.collection)
275
+ });
276
+ if (mode === "model") {
277
+ model += 1;
278
+ } else if (mode === "parent-child") {
279
+ parentChildSlugs.push(entry.slug);
280
+ } else {
281
+ unavailableSlugs.push(entry.slug);
282
+ }
283
+ return { ...entry, queryMode: mode, rules: entryRules };
284
+ });
285
+ return { entries: annotated, model, unavailableSlugs, parentChildSlugs };
286
+ }
287
+ function parentPathsForCollection(scan, collection) {
288
+ const paths = firestoreRulesAccessForCollection(scan, collection).paths;
289
+ const parents = /* @__PURE__ */ new Set();
290
+ for (const path of paths) {
291
+ const segments = path.split("/").filter((x) => x.length > 0);
292
+ const parentSegments = segments.slice(0, -2);
293
+ if (parentSegments.length > 0 && !parentSegments.some((x) => x.includes("=**"))) {
294
+ parents.add(parentSegments.join("/"));
295
+ }
296
+ }
297
+ return Array.from(parents);
298
+ }
299
+
65
300
  // packages/dbx-cli/src/lib/scan-helpers/exported-from-package.ts
66
301
  import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
67
302
  import { dirname as dirname2, isAbsolute, join, resolve } from "node:path";
@@ -225,10 +460,16 @@ function renderEntry({ entry, bound }) {
225
460
  entry.skip ? "skip: true" : void 0,
226
461
  entry.excluded ? "excluded: true" : void 0,
227
462
  entry.dispatcher ? "dispatcher: true" : void 0,
463
+ entry.queryMode ? `queryMode: ${JSON.stringify(entry.queryMode)}` : void 0,
464
+ entry.rules ? `rules: ${renderRules(entry.rules)}` : void 0,
228
465
  bound ? `factory: ${entry.name}` : void 0
229
466
  ];
230
467
  return ` { ${fields.filter(Boolean).join(", ")} }`;
231
468
  }
469
+ function renderRules(rules) {
470
+ const parts = [`list: ${JSON.stringify(rules.list)}`, `collectionGroup: ${rules.collectionGroup ? "true" : "false"}`, rules.reason ? `reason: ${JSON.stringify(rules.reason)}` : void 0, rules.parentPaths && rules.parentPaths.length > 0 ? `parentPaths: ${JSON.stringify(rules.parentPaths)}` : void 0];
471
+ return `{ ${parts.filter(Boolean).join(", ")} }`;
472
+ }
232
473
  function renderParams(params) {
233
474
  const items = params.map((param) => {
234
475
  const parts = [`name: ${JSON.stringify(param.name)}`, `type: ${JSON.stringify(param.type)}`, param.description ? `description: ${JSON.stringify(param.description)}` : void 0, `optional: ${param.optional ? "true" : "false"}`];
@@ -429,11 +670,18 @@ function analyzeSequence(input) {
429
670
  if (isSingleFieldQuery(buckets)) {
430
671
  return analyzeSingleField({ buckets, collection, scope, warnings });
431
672
  }
673
+ if (scope === "COLLECTION" && isEqualityOnlySequence(buckets)) {
674
+ warnings.push({ kind: "equality-only-composite-skipped", factoryName, fields: buckets.equalities.map((e) => e.fieldPath) });
675
+ return { kind: "auto", warnings };
676
+ }
432
677
  return analyzeMultiField({ buckets, sequenceEntries: sequence.entries, collection, scope, factoryName, warnings });
433
678
  }
434
679
  function isSingleFieldQuery(buckets) {
435
680
  return buckets.distinctFieldCount === 1;
436
681
  }
682
+ function isEqualityOnlySequence(buckets) {
683
+ return buckets.ranges.length === 0 && buckets.arrays.length === 0 && buckets.orderBys.length === 0 && buckets.equalities.length > 0 && buckets.equalities.every((e) => (e.operator ?? "==") === "==");
684
+ }
437
685
  function analyzeSingleField(input) {
438
686
  const { buckets, collection, scope, warnings } = input;
439
687
  if (scope === "COLLECTION") {
@@ -528,7 +776,7 @@ function collectOrderByDirections(input) {
528
776
  }
529
777
  }
530
778
  for (const [field, dirs] of conflicts) {
531
- warnings.push({ kind: "orderby-conflict", factoryName, field, directions: [...dirs] });
779
+ warnings.push({ kind: "orderby-conflict", factoryName, field, directions: Array.from(dirs) });
532
780
  }
533
781
  return out;
534
782
  }
@@ -1009,7 +1257,7 @@ function composeEntry(input) {
1009
1257
  }
1010
1258
  function resolveConstraintSequences(input) {
1011
1259
  const { candidate, tags, name, line, filePath, warnings } = input;
1012
- const bodyResult = extractConstraintsFromBody({ decl: candidate.decl, factoryName: name, filePath, dispatcher: tags.dispatcher });
1260
+ const bodyResult = extractConstraintsFromBody({ decl: candidate.decl, factoryName: name, filePath, dispatcher: tags.dispatcher, suppressed: tags.skip || tags.specOnly });
1013
1261
  for (const warning of bodyResult.warnings) {
1014
1262
  warnings.push(warning);
1015
1263
  }
@@ -1090,11 +1338,14 @@ function buildConstraintSequences(input) {
1090
1338
  return sequences;
1091
1339
  }
1092
1340
  function extractConstraintsFromBody(input) {
1093
- const { decl, factoryName, filePath, dispatcher } = input;
1341
+ const { decl, factoryName, filePath, dispatcher, suppressed } = input;
1094
1342
  const entries = [];
1095
1343
  const conditionalFieldSet = /* @__PURE__ */ new Set();
1096
1344
  const warnings = [];
1097
1345
  const body = decl.getBody();
1346
+ if (suppressed) {
1347
+ return { entries, conditionalFields: [], warnings, skipped: true, dispatcherDelegates: [] };
1348
+ }
1098
1349
  if (dispatcher) {
1099
1350
  const violation = body === void 0 ? void 0 : findFirstConstraintCall(body);
1100
1351
  if (violation !== void 0) {
@@ -1985,7 +2236,16 @@ async function main() {
1985
2236
  for (const warning of warnings) {
1986
2237
  console.warn(warning);
1987
2238
  }
1988
- const formatted = await renderQueryManifest({ outputFile, entries: collected, projectName, namespace });
2239
+ const queryModes = applyQueryModes({ collected, rules: flags.rules });
2240
+ if (queryModes.kind === "failure") {
2241
+ console.error(queryModes.message);
2242
+ process.exit(1);
2243
+ return;
2244
+ }
2245
+ for (const warning of queryModes.warnings) {
2246
+ console.warn(warning);
2247
+ }
2248
+ const formatted = await renderQueryManifest({ outputFile, entries: queryModes.entries, projectName, namespace });
1989
2249
  const relOutput = relative2(WORKSPACE_ROOT, outputFile);
1990
2250
  if (flags.check) {
1991
2251
  const current = existsSync3(outputFile) ? readFileSync3(outputFile, "utf8") : void 0;
@@ -2000,12 +2260,38 @@ async function main() {
2000
2260
  console.log(`[${outcome}] ${relOutput}`);
2001
2261
  }
2002
2262
  const boundCount = collected.filter((x) => x.bound).length;
2003
- console.log(`Summary: ${flags.components.length} component(s) \xB7 ${collected.length} entries \xB7 ${boundCount} invocable \xB7 ${collected.length - boundCount} unbound \xB7 ${droppedSpecOnly} spec-only dropped`);
2263
+ console.log(`Summary: ${flags.components.length} component(s) \xB7 ${collected.length} entries \xB7 ${boundCount} bound \xB7 ${collected.length - boundCount} unbound \xB7 ${droppedSpecOnly} spec-only dropped \xB7 rules: ${queryModes.summary}`);
2004
2264
  if (flags.strict && boundCount < collected.length) {
2005
2265
  console.error(`[strict] ${collected.length - boundCount} factor(y|ies) failed to bind \u2014 failing build.`);
2006
2266
  process.exit(1);
2007
2267
  }
2008
2268
  }
2269
+ function applyQueryModes(input) {
2270
+ const { collected, rules } = input;
2271
+ let result;
2272
+ if (rules) {
2273
+ const rulesFile = resolveWorkspacePath(rules);
2274
+ if (existsSync3(rulesFile)) {
2275
+ const annotated = annotateQueryEntryMode({ entries: collected.map((x) => x.entry), rulesSource: readFileSync3(rulesFile, "utf8") });
2276
+ const entries = annotated.entries.map((entry, index) => ({ ...collected[index], entry }));
2277
+ const warnings = annotated.unavailableSlugs.map((slug) => `[query-unavailable] ${slug} \u2014 no client can run this query; see \`firestore-queries ${slug}\`.`);
2278
+ if (annotated.parentChildSlugs.length > 0) {
2279
+ warnings.push(`[query-parent-child] ${annotated.parentChildSlugs.length} quer${annotated.parentChildSlugs.length === 1 ? "y" : "ies"} run only with --parent: ${annotated.parentChildSlugs.join(", ")}`);
2280
+ }
2281
+ result = {
2282
+ kind: "success",
2283
+ entries,
2284
+ warnings,
2285
+ summary: `${annotated.model} model \xB7 ${annotated.parentChildSlugs.length} parent-child \xB7 ${annotated.unavailableSlugs.length} unavailable`
2286
+ };
2287
+ } else {
2288
+ result = { kind: "failure", message: `[rules] ${relative2(WORKSPACE_ROOT, rulesFile)} does not exist \u2014 fix --rules or drop it.` };
2289
+ }
2290
+ } else {
2291
+ result = { kind: "success", entries: collected, warnings: [], summary: "not scanned (pass --rules=<firestore.rules> to resolve query modes)" };
2292
+ }
2293
+ return result;
2294
+ }
2009
2295
  function resolveWorkspacePath(value) {
2010
2296
  return isAbsolute2(value) ? value : resolve3(WORKSPACE_ROOT, value);
2011
2297
  }
@@ -2017,6 +2303,7 @@ function parseFlags(argv) {
2017
2303
  const components = [];
2018
2304
  let output;
2019
2305
  let project;
2306
+ let rules;
2020
2307
  let strict = false;
2021
2308
  let check = false;
2022
2309
  for (const arg of argv) {
@@ -2031,9 +2318,12 @@ function parseFlags(argv) {
2031
2318
  output = arg.slice("--output=".length);
2032
2319
  } else if (arg.startsWith("--project=")) {
2033
2320
  project = arg.slice("--project=".length);
2321
+ } else if (arg.startsWith("--rules=")) {
2322
+ const value = arg.slice("--rules=".length).trim();
2323
+ if (value) rules = value;
2034
2324
  }
2035
2325
  }
2036
- return { components, output, project, strict, check };
2326
+ return { components, output, project, rules, strict, check };
2037
2327
  }
2038
2328
  function printUsageAndExit() {
2039
2329
  console.error(String.raw`generate-firestore-query-manifest
@@ -2043,7 +2333,7 @@ Usage:
2043
2333
  --project=<name> \
2044
2334
  --component=<component-dir> [--component=<component-dir> ...] \
2045
2335
  --output=<path-to-query.manifest.generated.ts> \
2046
- [--strict] [--check]
2336
+ [--rules=<path-to-firestore.rules>] [--strict] [--check]
2047
2337
 
2048
2338
  Required flags:
2049
2339
  --component=<dir> A "-firebase" component root to scan. Repeatable.
@@ -2051,6 +2341,10 @@ Required flags:
2051
2341
 
2052
2342
  Optional:
2053
2343
  --project=<name> Project name for the regenerate banner; also derives the constant name.
2344
+ --rules=<path> The app's firestore.rules. Stamps each entry with how it must be invoked
2345
+ (model / parent-child / unavailable), so a catalogued-but-unrunnable query is
2346
+ flagged at generation time instead of surfacing as AUTH_FORBIDDEN at call
2347
+ time. Omitted => unknown.
2054
2348
  --strict Fail when any tagged factory is not exported from its component barrel.
2055
2349
  --check Do not write; fail when the committed file is out of date.`);
2056
2350
  process.exit(1);
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-firestore-query-manifest",
3
- "version": "13.40.0",
3
+ "version": "13.42.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.40.0",
11
- "@dereekb/util": "13.40.0"
10
+ "@dereekb/dbx-cli": "13.42.0",
11
+ "@dereekb/util": "13.42.0"
12
12
  }
13
13
  }