@blumintinc/eslint-plugin-blumint 1.16.2 → 1.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 (49) hide show
  1. package/README.md +21 -0
  2. package/lib/index.js +64 -1
  3. package/lib/rules/enforce-boolean-naming-prefixes.js +30 -14
  4. package/lib/rules/enforce-cloud-function-id-length.d.ts +5 -0
  5. package/lib/rules/enforce-cloud-function-id-length.js +104 -0
  6. package/lib/rules/enforce-is-prefix-validators.d.ts +13 -0
  7. package/lib/rules/enforce-is-prefix-validators.js +304 -0
  8. package/lib/rules/enforce-m3-sentence-case.d.ts +12 -0
  9. package/lib/rules/enforce-m3-sentence-case.js +430 -0
  10. package/lib/rules/enforce-snapshot-state-narrowing.d.ts +10 -0
  11. package/lib/rules/enforce-snapshot-state-narrowing.js +281 -0
  12. package/lib/rules/enforce-types-directory-placement.d.ts +9 -0
  13. package/lib/rules/enforce-types-directory-placement.js +276 -0
  14. package/lib/rules/no-direct-function-state.d.ts +8 -0
  15. package/lib/rules/no-direct-function-state.js +285 -0
  16. package/lib/rules/no-fill-template-mutation.d.ts +1 -0
  17. package/lib/rules/no-fill-template-mutation.js +324 -0
  18. package/lib/rules/no-portal-inside-tooltip.d.ts +10 -0
  19. package/lib/rules/no-portal-inside-tooltip.js +219 -0
  20. package/lib/rules/no-redundant-boolean-callback-props.d.ts +11 -0
  21. package/lib/rules/no-redundant-boolean-callback-props.js +355 -0
  22. package/lib/rules/no-satisfies-in-frontend-bundle.d.ts +8 -0
  23. package/lib/rules/no-satisfies-in-frontend-bundle.js +126 -0
  24. package/lib/rules/no-single-dismiss-dialog-button.d.ts +7 -0
  25. package/lib/rules/no-single-dismiss-dialog-button.js +127 -0
  26. package/lib/rules/no-stablehash-react-nodes.d.ts +1 -0
  27. package/lib/rules/no-stablehash-react-nodes.js +325 -0
  28. package/lib/rules/parallelize-loop-awaits.d.ts +8 -0
  29. package/lib/rules/parallelize-loop-awaits.js +582 -0
  30. package/lib/rules/prefer-flat-transform-each-keys.d.ts +1 -0
  31. package/lib/rules/prefer-flat-transform-each-keys.js +228 -0
  32. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +1 -0
  33. package/lib/rules/prefer-getter-over-parameterless-method.js +44 -0
  34. package/lib/rules/prefer-spread-over-reassembly.d.ts +5 -0
  35. package/lib/rules/prefer-spread-over-reassembly.js +401 -0
  36. package/lib/rules/prefer-sx-prop-over-system-props.d.ts +9 -0
  37. package/lib/rules/prefer-sx-prop-over-system-props.js +401 -0
  38. package/lib/rules/prefer-use-base62-id.d.ts +8 -0
  39. package/lib/rules/prefer-use-base62-id.js +483 -0
  40. package/lib/rules/prefer-use-theme.d.ts +1 -0
  41. package/lib/rules/prefer-use-theme.js +206 -0
  42. package/lib/rules/prefer-utility-function-own-file.d.ts +9 -0
  43. package/lib/rules/prefer-utility-function-own-file.js +505 -0
  44. package/lib/rules/require-props-composition.d.ts +10 -0
  45. package/lib/rules/require-props-composition.js +433 -0
  46. package/lib/rules/require-server-timestamp-for-firestore-dates.d.ts +9 -0
  47. package/lib/rules/require-server-timestamp-for-firestore-dates.js +313 -0
  48. package/package.json +1 -1
  49. package/release-manifest.json +190 -0
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireServerTimestampForFirestoreDates = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const minimatch_1 = require("minimatch");
6
+ const createRule_1 = require("../utils/createRule");
7
+ const DEFAULT_FIRESTORE_TYPE_PATHS = ['functions/src/types/firestore/**'];
8
+ const DEFAULT_TARGET_PATHS = ['src/**'];
9
+ const TEST_FILE_PATTERNS = [
10
+ /\.test\.[jt]sx?$/,
11
+ /\.spec\.[jt]sx?$/,
12
+ /__tests__\//,
13
+ /__mocks__\//,
14
+ ];
15
+ function isTestFile(filename) {
16
+ return TEST_FILE_PATTERNS.some((pattern) => pattern.test(filename));
17
+ }
18
+ function matchesAnyGlob(value, globs) {
19
+ return globs.some((glob) => {
20
+ const mm = new minimatch_1.Minimatch(glob, { matchBase: false });
21
+ return mm.match(value);
22
+ });
23
+ }
24
+ /**
25
+ * Checks whether the import source matches any of the configured Firestore
26
+ * type path globs. Handles both absolute paths like
27
+ * `functions/src/types/firestore/TokenMetadata` and relative paths like
28
+ * `../../functions/src/types/firestore/TokenMetadata` by normalising the
29
+ * path before testing.
30
+ */
31
+ function isFirestoreTypePath(source, firestoreTypePaths) {
32
+ // Strip leading relative segments so relative paths are tested the same way
33
+ // as bare module specifiers.
34
+ const normalized = source.replace(/^(\.\.\/|\.\/)+/, '');
35
+ return matchesAnyGlob(normalized, firestoreTypePaths);
36
+ }
37
+ /**
38
+ * Extracts the base type name (without generic parameters) from a
39
+ * TSTypeReference node. For `TokenMetadata<'offchain', Date>` this returns
40
+ * `TokenMetadata`.
41
+ */
42
+ function getBaseTypeName(node) {
43
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.Identifier) {
44
+ return node.typeName.name;
45
+ }
46
+ // Handle qualified names like `NS.TypeName`
47
+ if (node.typeName.type === utils_1.AST_NODE_TYPES.TSQualifiedName) {
48
+ return node.typeName.right.name;
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * Recursively collects all TSTypeReference base names from a type node,
54
+ * including type parameters. This lets us detect Firestore types nested
55
+ * inside utility types like `Partial<TokenMetadata<...>>`.
56
+ */
57
+ function collectTypeReferenceNames(typeNode) {
58
+ if (!typeNode)
59
+ return [];
60
+ const names = [];
61
+ if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
62
+ const base = getBaseTypeName(typeNode);
63
+ if (base)
64
+ names.push(base);
65
+ if (typeNode.typeParameters) {
66
+ for (const param of typeNode.typeParameters.params) {
67
+ names.push(...collectTypeReferenceNames(param));
68
+ }
69
+ }
70
+ }
71
+ else if (typeNode.type === utils_1.AST_NODE_TYPES.TSArrayType) {
72
+ names.push(...collectTypeReferenceNames(typeNode.elementType));
73
+ }
74
+ else if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType ||
75
+ typeNode.type === utils_1.AST_NODE_TYPES.TSIntersectionType) {
76
+ for (const t of typeNode.types) {
77
+ names.push(...collectTypeReferenceNames(t));
78
+ }
79
+ }
80
+ else if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeOperator) {
81
+ names.push(...collectTypeReferenceNames(typeNode.typeAnnotation));
82
+ }
83
+ return names;
84
+ }
85
+ /**
86
+ * Determines whether a type annotation references any of the tracked Firestore
87
+ * type names. The check is purely syntactic — we look for type names in
88
+ * TSTypeReference nodes.
89
+ */
90
+ function typeAnnotationReferencesFirestoreType(typeAnnotation, firestoreTypeNames) {
91
+ const names = collectTypeReferenceNames(typeAnnotation);
92
+ return names.some((n) => firestoreTypeNames.has(n));
93
+ }
94
+ /**
95
+ * Returns the inner expression, unwrapping TSAsExpression / TSSatisfiesExpression
96
+ * chains, so we can inspect what lies under a cast.
97
+ */
98
+ function unwrapCast(node) {
99
+ let current = node;
100
+ while (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
101
+ current.type ===
102
+ utils_1.AST_NODE_TYPES
103
+ .TSSatisfiesExpression) {
104
+ current = current.expression;
105
+ }
106
+ return current;
107
+ }
108
+ /**
109
+ * Checks whether an expression is `new Date(...)` (possibly wrapped in casts).
110
+ */
111
+ function isNewDate(node) {
112
+ const unwrapped = unwrapCast(node);
113
+ if (unwrapped.type !== utils_1.AST_NODE_TYPES.NewExpression)
114
+ return false;
115
+ const callee = unwrapped.callee;
116
+ return callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === 'Date';
117
+ }
118
+ /**
119
+ * Recursively visits all property values in an ObjectExpression (including
120
+ * nested objects) and reports any `new Date()` calls.
121
+ */
122
+ function reportNewDatesInObject(obj,
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ context) {
125
+ for (const prop of obj.properties) {
126
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
127
+ continue;
128
+ const value = prop.value;
129
+ if (isNewDate(value)) {
130
+ context.report({
131
+ node: value,
132
+ messageId: 'useServerTimestamp',
133
+ });
134
+ }
135
+ else if (value.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
136
+ reportNewDatesInObject(value, context);
137
+ }
138
+ else if (value.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
139
+ value.type ===
140
+ utils_1.AST_NODE_TYPES
141
+ .TSSatisfiesExpression) {
142
+ // The cast may wrap a new Date() OR an object literal we need to recurse into
143
+ const inner = unwrapCast(value);
144
+ if (isNewDate(inner)) {
145
+ context.report({
146
+ node: value,
147
+ messageId: 'useServerTimestamp',
148
+ });
149
+ }
150
+ else if (inner.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
151
+ reportNewDatesInObject(inner, context);
152
+ }
153
+ }
154
+ else if (value.type === utils_1.AST_NODE_TYPES.ConditionalExpression) {
155
+ // Ternary: check both branches
156
+ if (isNewDate(value.consequent)) {
157
+ context.report({
158
+ node: value.consequent,
159
+ messageId: 'useServerTimestamp',
160
+ });
161
+ }
162
+ if (isNewDate(value.alternate)) {
163
+ context.report({
164
+ node: value.alternate,
165
+ messageId: 'useServerTimestamp',
166
+ });
167
+ }
168
+ }
169
+ }
170
+ }
171
+ exports.requireServerTimestampForFirestoreDates = (0, createRule_1.createRule)({
172
+ name: 'require-server-timestamp-for-firestore-dates',
173
+ meta: {
174
+ type: 'problem',
175
+ docs: {
176
+ description: 'Enforce serverTimestamp() instead of new Date() for Firestore timestamp fields',
177
+ recommended: 'error',
178
+ },
179
+ schema: [
180
+ {
181
+ type: 'object',
182
+ properties: {
183
+ firestoreTypePaths: {
184
+ type: 'array',
185
+ items: { type: 'string' },
186
+ },
187
+ targetPaths: {
188
+ type: 'array',
189
+ items: { type: 'string' },
190
+ },
191
+ ignoreTestFiles: {
192
+ type: 'boolean',
193
+ },
194
+ },
195
+ additionalProperties: false,
196
+ },
197
+ ],
198
+ messages: {
199
+ useServerTimestamp: 'Use serverTimestamp() instead of new Date() for Firestore timestamp fields — client clocks are unreliable and spoofable.',
200
+ },
201
+ },
202
+ defaultOptions: [{}],
203
+ create(context, [options]) {
204
+ const firestoreTypePaths = options.firestoreTypePaths ?? DEFAULT_FIRESTORE_TYPE_PATHS;
205
+ const targetPaths = options.targetPaths ?? DEFAULT_TARGET_PATHS;
206
+ const ignoreTestFiles = options.ignoreTestFiles ?? true;
207
+ const filename = context.getFilename();
208
+ // Honour ignoreTestFiles option
209
+ if (ignoreTestFiles && isTestFile(filename)) {
210
+ return {};
211
+ }
212
+ // Only apply rule to target paths (frontend code by default).
213
+ // Normalise to forward slashes so Windows paths work. Test filenames are
214
+ // relative (e.g. `src/hooks/useExample.ts`). Real monorepo filenames are
215
+ // absolute (e.g. `/workspace/src/hooks/useExample.ts`); for those we also
216
+ // try matching the segment of the path that starts with the first literal
217
+ // prefix of each glob (only when the filename is an absolute path).
218
+ const normalizedFilename = filename.replace(/\\/g, '/');
219
+ const isAbsolutePath = normalizedFilename.startsWith('/');
220
+ const inTargetPaths = matchesAnyGlob(normalizedFilename, targetPaths) ||
221
+ (isAbsolutePath &&
222
+ targetPaths.some((glob) => {
223
+ const prefix = glob.replace(/\*.*$/, ''); // e.g. 'src/'
224
+ const idx = normalizedFilename.indexOf('/' + prefix);
225
+ if (idx === -1)
226
+ return false;
227
+ const suffix = normalizedFilename.slice(idx + 1); // e.g. 'src/hooks/...'
228
+ return matchesAnyGlob(suffix, [glob]);
229
+ }));
230
+ if (!inTargetPaths)
231
+ return {};
232
+ // Collect Firestore type names from imports in this file
233
+ const firestoreTypeNames = new Set();
234
+ return {
235
+ ImportDeclaration(node) {
236
+ const source = node.source.value;
237
+ if (!isFirestoreTypePath(source, firestoreTypePaths))
238
+ return;
239
+ for (const specifier of node.specifiers) {
240
+ if (specifier.type === utils_1.AST_NODE_TYPES.ImportSpecifier ||
241
+ specifier.type === utils_1.AST_NODE_TYPES.ImportDefaultSpecifier ||
242
+ specifier.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
243
+ // Track the local alias name (what this file uses to refer to the type)
244
+ firestoreTypeNames.add(specifier.local.name);
245
+ }
246
+ }
247
+ },
248
+ VariableDeclarator(node) {
249
+ if (firestoreTypeNames.size === 0)
250
+ return;
251
+ // Pattern: const x: FirestoreType = { ... }
252
+ const typeAnnotation = node.id.typeAnnotation?.typeAnnotation;
253
+ if (typeAnnotation &&
254
+ typeAnnotationReferencesFirestoreType(typeAnnotation, firestoreTypeNames) &&
255
+ node.init &&
256
+ node.init.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
257
+ reportNewDatesInObject(node.init, context);
258
+ }
259
+ },
260
+ // Pattern: { ... } as FirestoreType or { ... } satisfies FirestoreType
261
+ TSAsExpression(node) {
262
+ if (firestoreTypeNames.size === 0)
263
+ return;
264
+ // Only check when the as-cast target type references a Firestore type
265
+ const typeAnnotation = node.typeAnnotation;
266
+ if (!typeAnnotationReferencesFirestoreType(typeAnnotation, firestoreTypeNames))
267
+ return;
268
+ const inner = unwrapCast(node.expression);
269
+ if (inner.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
270
+ reportNewDatesInObject(inner, context);
271
+ }
272
+ },
273
+ // Arrow functions with concise body: `const f = (): FirestoreType => ({ ... })`
274
+ // In this form the body is the ObjectExpression directly (no ReturnStatement).
275
+ ArrowFunctionExpression(node) {
276
+ if (firestoreTypeNames.size === 0)
277
+ return;
278
+ if (node.body.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
279
+ return;
280
+ const returnType = node.returnType?.typeAnnotation;
281
+ if (returnType &&
282
+ typeAnnotationReferencesFirestoreType(returnType, firestoreTypeNames)) {
283
+ reportNewDatesInObject(node.body, context);
284
+ }
285
+ },
286
+ // Return statements in functions with explicit Firestore return type annotation
287
+ ReturnStatement(node) {
288
+ if (firestoreTypeNames.size === 0)
289
+ return;
290
+ if (!node.argument)
291
+ return;
292
+ if (node.argument.type !== utils_1.AST_NODE_TYPES.ObjectExpression)
293
+ return;
294
+ // Walk up to find the enclosing function and check its return type
295
+ let ancestor = node.parent;
296
+ while (ancestor) {
297
+ if (ancestor.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
298
+ ancestor.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
299
+ ancestor.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
300
+ const returnType = ancestor.returnType?.typeAnnotation;
301
+ if (returnType &&
302
+ typeAnnotationReferencesFirestoreType(returnType, firestoreTypeNames)) {
303
+ reportNewDatesInObject(node.argument, context);
304
+ }
305
+ break;
306
+ }
307
+ ancestor = ancestor.parent;
308
+ }
309
+ },
310
+ };
311
+ },
312
+ });
313
+ //# sourceMappingURL=require-server-timestamp-for-firestore-dates.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.16.2",
3
+ "version": "1.17.0",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,194 @@
1
1
  [
2
+ {
3
+ "version": "1.17.0",
4
+ "date": "2026-07-01T05:16:18.225Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-boolean-naming-prefixes",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1249
11
+ ],
12
+ "summary": "require boolean prefix at a name boundary in callExpressionLooksBoolean (closes #1249)"
13
+ },
14
+ {
15
+ "name": "enforce-cloud-function-id-length",
16
+ "changeType": "feat",
17
+ "issues": [
18
+ 1222
19
+ ],
20
+ "summary": "flag .f.ts paths deriving Firebase IDs over 62 chars (closes #1222)"
21
+ },
22
+ {
23
+ "name": "enforce-is-prefix-validators",
24
+ "changeType": "feat",
25
+ "issues": [
26
+ 1193
27
+ ],
28
+ "summary": "require is-prefix on exported validators (closes #1193)"
29
+ },
30
+ {
31
+ "name": "enforce-m3-sentence-case",
32
+ "changeType": "feat",
33
+ "issues": [
34
+ 1190
35
+ ],
36
+ "summary": "warn on Title Case / ALL CAPS user-facing text (closes #1190)"
37
+ },
38
+ {
39
+ "name": "enforce-snapshot-state-narrowing",
40
+ "changeType": "feat",
41
+ "issues": [
42
+ 1210
43
+ ],
44
+ "summary": "require isSnapshotReady over falsy/typeof checks (closes #1210)"
45
+ },
46
+ {
47
+ "name": "enforce-types-directory-placement",
48
+ "changeType": "feat",
49
+ "issues": [
50
+ 1194
51
+ ],
52
+ "summary": "flag type-only files outside functions/src/types (closes #1194)"
53
+ },
54
+ {
55
+ "name": "no-direct-function-state",
56
+ "changeType": "feat",
57
+ "issues": [
58
+ 1208
59
+ ],
60
+ "summary": "flag functions passed directly to useState setters (closes #1208)"
61
+ },
62
+ {
63
+ "name": "no-fill-template-mutation",
64
+ "changeType": "feat",
65
+ "issues": [
66
+ 1209
67
+ ],
68
+ "summary": "forbid mutating fillTemplate() results (closes #1209)"
69
+ },
70
+ {
71
+ "name": "no-portal-inside-tooltip",
72
+ "changeType": "feat",
73
+ "issues": [
74
+ 1223
75
+ ],
76
+ "summary": "flag portals nested in tooltip wrappers (closes #1223)"
77
+ },
78
+ {
79
+ "name": "no-redundant-boolean-callback-props",
80
+ "changeType": "feat",
81
+ "issues": [
82
+ 1192
83
+ ],
84
+ "summary": "flag boolean props redundant with a callback's presence (closes #1192)"
85
+ },
86
+ {
87
+ "name": "no-satisfies-in-frontend-bundle",
88
+ "changeType": "feat",
89
+ "issues": [
90
+ 1226
91
+ ],
92
+ "summary": "ban satisfies in webpack-bundled files (closes #1226)"
93
+ },
94
+ {
95
+ "name": "no-single-dismiss-dialog-button",
96
+ "changeType": "feat",
97
+ "issues": [
98
+ 1221
99
+ ],
100
+ "summary": "flag lone dismiss button in dialog buttons array (closes #1221)"
101
+ },
102
+ {
103
+ "name": "no-stablehash-react-nodes",
104
+ "changeType": "feat",
105
+ "issues": [
106
+ 1134
107
+ ],
108
+ "summary": "flag stableHash() on ReactNodes/KeyedNodes (closes #1134)"
109
+ },
110
+ {
111
+ "name": "parallelize-loop-awaits",
112
+ "changeType": "feat",
113
+ "issues": [
114
+ 1184
115
+ ],
116
+ "summary": "flag sequential await in loops parallelizable via Promise.all (closes #1184)"
117
+ },
118
+ {
119
+ "name": "prefer-flat-transform-each-keys",
120
+ "changeType": "feat",
121
+ "issues": [
122
+ 1212
123
+ ],
124
+ "summary": "flag nested objects in propagation transformEach returns (closes #1212)"
125
+ },
126
+ {
127
+ "name": "prefer-getter-over-parameterless-method",
128
+ "changeType": "fix",
129
+ "issues": [
130
+ 1248
131
+ ],
132
+ "summary": "exempt throwing and builder/factory methods (closes #1248)"
133
+ },
134
+ {
135
+ "name": "prefer-spread-over-reassembly",
136
+ "changeType": "feat",
137
+ "issues": [
138
+ 1188
139
+ ],
140
+ "summary": "flag destructure-then-reassemble prop forwarding (closes #1188)"
141
+ },
142
+ {
143
+ "name": "prefer-sx-prop-over-system-props",
144
+ "changeType": "feat",
145
+ "issues": [
146
+ 1189
147
+ ],
148
+ "summary": "migrate deprecated MUI system props into sx (closes #1189)"
149
+ },
150
+ {
151
+ "name": "prefer-use-base62-id",
152
+ "changeType": "feat",
153
+ "issues": [
154
+ 1206
155
+ ],
156
+ "summary": "prefer useBase62Id() over useState/useRef/useMemo + uuidv4Base62() (closes #1206)"
157
+ },
158
+ {
159
+ "name": "prefer-use-theme",
160
+ "changeType": "feat",
161
+ "issues": [
162
+ 1213
163
+ ],
164
+ "summary": "flag direct theme-constant imports over useTheme() (closes #1213)"
165
+ },
166
+ {
167
+ "name": "prefer-utility-function-own-file",
168
+ "changeType": "feat",
169
+ "issues": [
170
+ 1234
171
+ ],
172
+ "summary": "flag sizable co-located utility functions (closes #1234)"
173
+ },
174
+ {
175
+ "name": "require-props-composition",
176
+ "changeType": "feat",
177
+ "issues": [
178
+ 1181
179
+ ],
180
+ "summary": "flag component Props that don't compose with rendered children (closes #1181)"
181
+ },
182
+ {
183
+ "name": "require-server-timestamp-for-firestore-dates",
184
+ "changeType": "feat",
185
+ "issues": [
186
+ 1183
187
+ ],
188
+ "summary": "flag new Date() in Firestore-typed objects (closes #1183)"
189
+ }
190
+ ]
191
+ },
2
192
  {
3
193
  "version": "1.16.2",
4
194
  "date": "2026-06-30T22:17:37.457Z",