@dereekb/dbx-cli 13.40.0 → 13.41.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/eslint/package.json +3 -3
- package/firebase-api-manifest/package.json +3 -3
- package/firestore-query-manifest/main.js +291 -7
- package/firestore-query-manifest/package.json +3 -3
- package/generate-firestore-indexes/main.js +2 -2
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/package.json +3 -3
- package/generate-route-manifest/package.json +2 -2
- package/index.esm.js +500 -124
- package/lint-cache/package.json +2 -2
- package/manifest-extract/package.json +2 -2
- package/model-test/package.json +2 -2
- package/package.json +6 -6
- package/route/package.json +7 -7
- package/src/lib/doctor/firestore-session.check.d.ts +23 -2
- package/src/lib/firestore/firestore.collection.d.ts +29 -0
- package/src/lib/firestore/firestore.query.d.ts +1 -1
- package/src/lib/firestore/index.d.ts +1 -0
- package/src/lib/firestore/query-info-utils.d.ts +6 -1
- package/src/lib/firestore/query-mode.d.ts +120 -0
- package/src/lib/manifest/types.d.ts +109 -0
- package/test/package.json +9 -9
- package/validate/package.json +3 -3
package/eslint/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/eslint",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"peerDependencies": {
|
|
6
|
-
"@dereekb/dbx-cli": "13.
|
|
7
|
-
"@dereekb/util": "13.
|
|
6
|
+
"@dereekb/dbx-cli": "13.41.0",
|
|
7
|
+
"@dereekb/util": "13.41.0",
|
|
8
8
|
"@typescript-eslint/utils": "8.59.3",
|
|
9
9
|
"typescript": "5.9.3"
|
|
10
10
|
},
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firebase-api-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.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.41.0",
|
|
11
|
+
"@dereekb/util": "13.41.0",
|
|
12
12
|
"prettier": "3.8.3"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -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.
|
|
12
|
+
version: "13.41.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.
|
|
20
|
-
"@dereekb/util": "13.
|
|
19
|
+
"@dereekb/dbx-cli": "13.41.0",
|
|
20
|
+
"@dereekb/util": "13.41.0"
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -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 = [...accumulated.entries()].map(([collection, value]) => ({
|
|
86
|
+
collection,
|
|
87
|
+
paths: [...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 [...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"}`];
|
|
@@ -1985,7 +2226,16 @@ async function main() {
|
|
|
1985
2226
|
for (const warning of warnings) {
|
|
1986
2227
|
console.warn(warning);
|
|
1987
2228
|
}
|
|
1988
|
-
const
|
|
2229
|
+
const queryModes = applyQueryModes({ collected, rules: flags.rules });
|
|
2230
|
+
if (queryModes.kind === "failure") {
|
|
2231
|
+
console.error(queryModes.message);
|
|
2232
|
+
process.exit(1);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
for (const warning of queryModes.warnings) {
|
|
2236
|
+
console.warn(warning);
|
|
2237
|
+
}
|
|
2238
|
+
const formatted = await renderQueryManifest({ outputFile, entries: queryModes.entries, projectName, namespace });
|
|
1989
2239
|
const relOutput = relative2(WORKSPACE_ROOT, outputFile);
|
|
1990
2240
|
if (flags.check) {
|
|
1991
2241
|
const current = existsSync3(outputFile) ? readFileSync3(outputFile, "utf8") : void 0;
|
|
@@ -2000,12 +2250,38 @@ async function main() {
|
|
|
2000
2250
|
console.log(`[${outcome}] ${relOutput}`);
|
|
2001
2251
|
}
|
|
2002
2252
|
const boundCount = collected.filter((x) => x.bound).length;
|
|
2003
|
-
console.log(`Summary: ${flags.components.length} component(s) \xB7 ${collected.length} entries \xB7 ${boundCount}
|
|
2253
|
+
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
2254
|
if (flags.strict && boundCount < collected.length) {
|
|
2005
2255
|
console.error(`[strict] ${collected.length - boundCount} factor(y|ies) failed to bind \u2014 failing build.`);
|
|
2006
2256
|
process.exit(1);
|
|
2007
2257
|
}
|
|
2008
2258
|
}
|
|
2259
|
+
function applyQueryModes(input) {
|
|
2260
|
+
const { collected, rules } = input;
|
|
2261
|
+
let result;
|
|
2262
|
+
if (rules) {
|
|
2263
|
+
const rulesFile = resolveWorkspacePath(rules);
|
|
2264
|
+
if (existsSync3(rulesFile)) {
|
|
2265
|
+
const annotated = annotateQueryEntryMode({ entries: collected.map((x) => x.entry), rulesSource: readFileSync3(rulesFile, "utf8") });
|
|
2266
|
+
const entries = annotated.entries.map((entry, index) => ({ ...collected[index], entry }));
|
|
2267
|
+
const warnings = annotated.unavailableSlugs.map((slug) => `[query-unavailable] ${slug} \u2014 no client can run this query; see \`firestore-queries ${slug}\`.`);
|
|
2268
|
+
if (annotated.parentChildSlugs.length > 0) {
|
|
2269
|
+
warnings.push(`[query-parent-child] ${annotated.parentChildSlugs.length} quer${annotated.parentChildSlugs.length === 1 ? "y" : "ies"} run only with --parent: ${annotated.parentChildSlugs.join(", ")}`);
|
|
2270
|
+
}
|
|
2271
|
+
result = {
|
|
2272
|
+
kind: "success",
|
|
2273
|
+
entries,
|
|
2274
|
+
warnings,
|
|
2275
|
+
summary: `${annotated.model} model \xB7 ${annotated.parentChildSlugs.length} parent-child \xB7 ${annotated.unavailableSlugs.length} unavailable`
|
|
2276
|
+
};
|
|
2277
|
+
} else {
|
|
2278
|
+
result = { kind: "failure", message: `[rules] ${relative2(WORKSPACE_ROOT, rulesFile)} does not exist \u2014 fix --rules or drop it.` };
|
|
2279
|
+
}
|
|
2280
|
+
} else {
|
|
2281
|
+
result = { kind: "success", entries: collected, warnings: [], summary: "not scanned (pass --rules=<firestore.rules> to resolve query modes)" };
|
|
2282
|
+
}
|
|
2283
|
+
return result;
|
|
2284
|
+
}
|
|
2009
2285
|
function resolveWorkspacePath(value) {
|
|
2010
2286
|
return isAbsolute2(value) ? value : resolve3(WORKSPACE_ROOT, value);
|
|
2011
2287
|
}
|
|
@@ -2017,6 +2293,7 @@ function parseFlags(argv) {
|
|
|
2017
2293
|
const components = [];
|
|
2018
2294
|
let output;
|
|
2019
2295
|
let project;
|
|
2296
|
+
let rules;
|
|
2020
2297
|
let strict = false;
|
|
2021
2298
|
let check = false;
|
|
2022
2299
|
for (const arg of argv) {
|
|
@@ -2031,9 +2308,12 @@ function parseFlags(argv) {
|
|
|
2031
2308
|
output = arg.slice("--output=".length);
|
|
2032
2309
|
} else if (arg.startsWith("--project=")) {
|
|
2033
2310
|
project = arg.slice("--project=".length);
|
|
2311
|
+
} else if (arg.startsWith("--rules=")) {
|
|
2312
|
+
const value = arg.slice("--rules=".length).trim();
|
|
2313
|
+
if (value) rules = value;
|
|
2034
2314
|
}
|
|
2035
2315
|
}
|
|
2036
|
-
return { components, output, project, strict, check };
|
|
2316
|
+
return { components, output, project, rules, strict, check };
|
|
2037
2317
|
}
|
|
2038
2318
|
function printUsageAndExit() {
|
|
2039
2319
|
console.error(String.raw`generate-firestore-query-manifest
|
|
@@ -2043,7 +2323,7 @@ Usage:
|
|
|
2043
2323
|
--project=<name> \
|
|
2044
2324
|
--component=<component-dir> [--component=<component-dir> ...] \
|
|
2045
2325
|
--output=<path-to-query.manifest.generated.ts> \
|
|
2046
|
-
[--strict] [--check]
|
|
2326
|
+
[--rules=<path-to-firestore.rules>] [--strict] [--check]
|
|
2047
2327
|
|
|
2048
2328
|
Required flags:
|
|
2049
2329
|
--component=<dir> A "-firebase" component root to scan. Repeatable.
|
|
@@ -2051,6 +2331,10 @@ Required flags:
|
|
|
2051
2331
|
|
|
2052
2332
|
Optional:
|
|
2053
2333
|
--project=<name> Project name for the regenerate banner; also derives the constant name.
|
|
2334
|
+
--rules=<path> The app's firestore.rules. Stamps each entry with how it must be invoked
|
|
2335
|
+
(model / parent-child / unavailable), so a catalogued-but-unrunnable query is
|
|
2336
|
+
flagged at generation time instead of surfacing as AUTH_FORBIDDEN at call
|
|
2337
|
+
time. Omitted => unknown.
|
|
2054
2338
|
--strict Fail when any tagged factory is not exported from its component barrel.
|
|
2055
2339
|
--check Do not write; fail when the committed file is out of date.`);
|
|
2056
2340
|
process.exit(1);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firestore-query-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.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.
|
|
11
|
-
"@dereekb/util": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.41.0",
|
|
11
|
+
"@dereekb/util": "13.41.0"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -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.41.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.41.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.
|
|
3
|
+
"version": "13.41.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.41.0"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-mcp-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.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.41.0",
|
|
8
|
+
"@dereekb/model": "13.41.0",
|
|
9
9
|
"arktype": "^2.2.0",
|
|
10
10
|
"jiti": "2.6.1"
|
|
11
11
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-route-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.41.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@dereekb/dbx-cli": "13.
|
|
7
|
+
"@dereekb/dbx-cli": "13.41.0",
|
|
8
8
|
"ts-morph": "^21.0.0"
|
|
9
9
|
}
|
|
10
10
|
}
|