@dereekb/firebase 13.12.4 → 13.12.6
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/index.cjs.js +415 -1
- package/eslint/index.esm.js +415 -4
- package/eslint/package.json +3 -3
- package/eslint/src/lib/index.d.ts +2 -0
- package/eslint/src/lib/plugin.d.ts +4 -0
- package/eslint/src/lib/require-api-crud-spec-for-group.rule.d.ts +47 -0
- package/eslint/src/lib/require-canonical-api-spec-filename.rule.d.ts +55 -0
- package/index.cjs.js +295 -87
- package/index.esm.js +291 -89
- package/package.json +5 -5
- package/src/lib/client/error/error.d.ts +6 -0
- package/src/lib/client/function/error.d.ts +6 -0
- package/src/lib/client/function/function.callable.d.ts +6 -0
- package/src/lib/common/auth/auth.d.ts +59 -0
- package/src/lib/common/auth/auth.error.d.ts +6 -0
- package/src/lib/model/oidcmodel/oidcmodel.interaction.d.ts +102 -15
- package/test/package.json +6 -6
package/eslint/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { existsSync, readFileSync, globSync } from 'node:fs';
|
|
3
|
-
import { join, dirname, isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { existsSync, readFileSync, globSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join, dirname, isAbsolute, resolve, sep, basename } from 'node:path';
|
|
4
4
|
import { parse as parse$1 } from '@typescript-eslint/typescript-estree';
|
|
5
5
|
import { parse as parse$2 } from '@typescript-eslint/parser';
|
|
6
6
|
|
|
@@ -17227,6 +17227,415 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
|
|
|
17227
17227
|
}
|
|
17228
17228
|
};
|
|
17229
17229
|
|
|
17230
|
+
var SPEC_SUFFIX = '.spec.ts';
|
|
17231
|
+
/**
|
|
17232
|
+
* Classifies a spec filename against the conventions for the given parent
|
|
17233
|
+
* folder. Pure function — never touches the filesystem.
|
|
17234
|
+
*
|
|
17235
|
+
* @param config - Inputs.
|
|
17236
|
+
* @param config.filename - Bare filename including the `.spec.ts` suffix
|
|
17237
|
+
* (e.g. `job.scenario.requirement.spec.ts`).
|
|
17238
|
+
* @param config.parentFolderName - Name of the directory containing the file
|
|
17239
|
+
* (e.g. `job`). Used to detect cross-group misplacement.
|
|
17240
|
+
* @returns The classification.
|
|
17241
|
+
*/ function classifySpecFile(config) {
|
|
17242
|
+
var filename = config.filename, parentFolderName = config.parentFolderName;
|
|
17243
|
+
var result;
|
|
17244
|
+
if (!filename.endsWith(SPEC_SUFFIX)) {
|
|
17245
|
+
result = {
|
|
17246
|
+
filename: filename,
|
|
17247
|
+
group: '',
|
|
17248
|
+
kind: 'non-spec',
|
|
17249
|
+
subgroups: [],
|
|
17250
|
+
isCanonical: false
|
|
17251
|
+
};
|
|
17252
|
+
} else {
|
|
17253
|
+
var _parts_;
|
|
17254
|
+
var stem = filename.slice(0, -SPEC_SUFFIX.length);
|
|
17255
|
+
var parts = stem.split('.');
|
|
17256
|
+
var group = (_parts_ = parts[0]) !== null && _parts_ !== void 0 ? _parts_ : '';
|
|
17257
|
+
if (group !== parentFolderName) {
|
|
17258
|
+
result = {
|
|
17259
|
+
filename: filename,
|
|
17260
|
+
group: group,
|
|
17261
|
+
kind: 'non-group',
|
|
17262
|
+
subgroups: [],
|
|
17263
|
+
isCanonical: false
|
|
17264
|
+
};
|
|
17265
|
+
} else {
|
|
17266
|
+
var rest = parts.slice(1);
|
|
17267
|
+
result = classifyRemainingSegments({
|
|
17268
|
+
filename: filename,
|
|
17269
|
+
group: group,
|
|
17270
|
+
rest: rest
|
|
17271
|
+
});
|
|
17272
|
+
}
|
|
17273
|
+
}
|
|
17274
|
+
return result;
|
|
17275
|
+
}
|
|
17276
|
+
function classifyRemainingSegments(config) {
|
|
17277
|
+
var filename = config.filename, group = config.group, rest = config.rest;
|
|
17278
|
+
var result;
|
|
17279
|
+
var crudIdx = rest.indexOf('crud');
|
|
17280
|
+
var scenarioIdx = rest.indexOf('scenario');
|
|
17281
|
+
if (crudIdx === 0) {
|
|
17282
|
+
if (rest.length === 1) {
|
|
17283
|
+
result = {
|
|
17284
|
+
filename: filename,
|
|
17285
|
+
group: group,
|
|
17286
|
+
kind: 'crud',
|
|
17287
|
+
subgroups: [],
|
|
17288
|
+
isCanonical: true
|
|
17289
|
+
};
|
|
17290
|
+
} else {
|
|
17291
|
+
result = {
|
|
17292
|
+
filename: filename,
|
|
17293
|
+
group: group,
|
|
17294
|
+
kind: 'crud-subgroup',
|
|
17295
|
+
subgroups: rest.slice(1),
|
|
17296
|
+
isCanonical: true
|
|
17297
|
+
};
|
|
17298
|
+
}
|
|
17299
|
+
} else if (scenarioIdx === 0) {
|
|
17300
|
+
if (rest.length === 1) {
|
|
17301
|
+
result = {
|
|
17302
|
+
filename: filename,
|
|
17303
|
+
group: group,
|
|
17304
|
+
kind: 'scenario',
|
|
17305
|
+
subgroups: [],
|
|
17306
|
+
isCanonical: true
|
|
17307
|
+
};
|
|
17308
|
+
} else {
|
|
17309
|
+
result = {
|
|
17310
|
+
filename: filename,
|
|
17311
|
+
group: group,
|
|
17312
|
+
kind: 'scenario-subgroup',
|
|
17313
|
+
subgroups: rest.slice(1),
|
|
17314
|
+
isCanonical: true
|
|
17315
|
+
};
|
|
17316
|
+
}
|
|
17317
|
+
} else if (crudIdx > 0) {
|
|
17318
|
+
var subgroups = rest.filter(function(_, i) {
|
|
17319
|
+
return i !== crudIdx;
|
|
17320
|
+
});
|
|
17321
|
+
var recommendedRename = buildCanonicalFilename({
|
|
17322
|
+
group: group,
|
|
17323
|
+
bucket: 'crud',
|
|
17324
|
+
subgroups: subgroups
|
|
17325
|
+
});
|
|
17326
|
+
result = {
|
|
17327
|
+
filename: filename,
|
|
17328
|
+
group: group,
|
|
17329
|
+
kind: 'crud-misplaced',
|
|
17330
|
+
subgroups: subgroups,
|
|
17331
|
+
isCanonical: false,
|
|
17332
|
+
recommendedRename: recommendedRename,
|
|
17333
|
+
driftReason: '`crud` segment is not directly after the group name.'
|
|
17334
|
+
};
|
|
17335
|
+
} else if (scenarioIdx > 0) {
|
|
17336
|
+
var subgroups1 = rest.filter(function(_, i) {
|
|
17337
|
+
return i !== scenarioIdx;
|
|
17338
|
+
});
|
|
17339
|
+
var recommendedRename1 = buildCanonicalFilename({
|
|
17340
|
+
group: group,
|
|
17341
|
+
bucket: 'scenario',
|
|
17342
|
+
subgroups: subgroups1
|
|
17343
|
+
});
|
|
17344
|
+
result = {
|
|
17345
|
+
filename: filename,
|
|
17346
|
+
group: group,
|
|
17347
|
+
kind: 'scenario-misplaced',
|
|
17348
|
+
subgroups: subgroups1,
|
|
17349
|
+
isCanonical: false,
|
|
17350
|
+
recommendedRename: recommendedRename1,
|
|
17351
|
+
driftReason: '`scenario` segment is not directly after the group name.'
|
|
17352
|
+
};
|
|
17353
|
+
} else if (rest.length === 0) {
|
|
17354
|
+
var recommendedRename2 = buildCanonicalFilename({
|
|
17355
|
+
group: group,
|
|
17356
|
+
bucket: 'scenario',
|
|
17357
|
+
subgroups: []
|
|
17358
|
+
});
|
|
17359
|
+
result = {
|
|
17360
|
+
filename: filename,
|
|
17361
|
+
group: group,
|
|
17362
|
+
kind: 'no-bucket',
|
|
17363
|
+
subgroups: [],
|
|
17364
|
+
isCanonical: false,
|
|
17365
|
+
recommendedRename: recommendedRename2,
|
|
17366
|
+
driftReason: 'Missing `crud` or `scenario` segment.'
|
|
17367
|
+
};
|
|
17368
|
+
} else {
|
|
17369
|
+
var recommendedRename3 = buildCanonicalFilename({
|
|
17370
|
+
group: group,
|
|
17371
|
+
bucket: 'scenario',
|
|
17372
|
+
subgroups: rest
|
|
17373
|
+
});
|
|
17374
|
+
result = {
|
|
17375
|
+
filename: filename,
|
|
17376
|
+
group: group,
|
|
17377
|
+
kind: 'no-bucket',
|
|
17378
|
+
subgroups: rest,
|
|
17379
|
+
isCanonical: false,
|
|
17380
|
+
recommendedRename: recommendedRename3,
|
|
17381
|
+
driftReason: 'Missing `crud` or `scenario` segment — defaulting suggestion to `scenario`.'
|
|
17382
|
+
};
|
|
17383
|
+
}
|
|
17384
|
+
return result;
|
|
17385
|
+
}
|
|
17386
|
+
/**
|
|
17387
|
+
* Renders a canonical spec filename for the given group + bucket + subgroup
|
|
17388
|
+
* chain. Pure data — used both by drift remediation and by the
|
|
17389
|
+
* `recommendSpecPath()` helper below.
|
|
17390
|
+
*
|
|
17391
|
+
* @param config - Inputs.
|
|
17392
|
+
* @param config.group - The model-group name (e.g. `job`).
|
|
17393
|
+
* @param config.bucket - `crud` or `scenario`.
|
|
17394
|
+
* @param config.subgroups - Optional ordered subgroup segments
|
|
17395
|
+
* (e.g. `['requirement','worker']`).
|
|
17396
|
+
* @returns The canonical filename (e.g. `job.scenario.requirement.worker.spec.ts`).
|
|
17397
|
+
*/ function buildCanonicalFilename(config) {
|
|
17398
|
+
var group = config.group, bucket = config.bucket, subgroups = config.subgroups;
|
|
17399
|
+
var tail = subgroups.length === 0 ? '' : ".".concat(subgroups.join('.'));
|
|
17400
|
+
return "".concat(group, ".").concat(bucket).concat(tail, ".spec.ts");
|
|
17401
|
+
}
|
|
17402
|
+
|
|
17403
|
+
/**
|
|
17404
|
+
* Default subpath segment (relative to an app source root) below which the
|
|
17405
|
+
* rule expects model-group function folders. Matches the convention used by
|
|
17406
|
+
* `<apiDir>/src/app/function/<group>/`.
|
|
17407
|
+
*/ var DEFAULT_FUNCTION_DIR_SEGMENT = 'src/app/function';
|
|
17408
|
+
function matchFunctionSpecPath(filename, functionDirSegment) {
|
|
17409
|
+
var result;
|
|
17410
|
+
if (filename.endsWith('.spec.ts')) {
|
|
17411
|
+
var normalized = filename.split(sep).join('/');
|
|
17412
|
+
var marker = "/".concat(functionDirSegment, "/");
|
|
17413
|
+
var markerIdx = normalized.indexOf(marker);
|
|
17414
|
+
if (markerIdx >= 0) {
|
|
17415
|
+
var afterMarker = normalized.slice(markerIdx + marker.length);
|
|
17416
|
+
var parts = afterMarker.split('/');
|
|
17417
|
+
if (parts.length === 2) {
|
|
17418
|
+
var _parts_, _parts_1;
|
|
17419
|
+
result = {
|
|
17420
|
+
filename: (_parts_ = parts[1]) !== null && _parts_ !== void 0 ? _parts_ : '',
|
|
17421
|
+
parentFolderName: (_parts_1 = parts[0]) !== null && _parts_1 !== void 0 ? _parts_1 : ''
|
|
17422
|
+
};
|
|
17423
|
+
} else if (parts.length > 2) {
|
|
17424
|
+
result = {
|
|
17425
|
+
filename: basename(filename),
|
|
17426
|
+
parentFolderName: basename(dirname(filename))
|
|
17427
|
+
};
|
|
17428
|
+
}
|
|
17429
|
+
}
|
|
17430
|
+
}
|
|
17431
|
+
return result;
|
|
17432
|
+
}
|
|
17433
|
+
/**
|
|
17434
|
+
* ESLint rule that enforces the canonical naming convention for Firebase
|
|
17435
|
+
* Functions API spec files: every `.spec.ts` under
|
|
17436
|
+
* `<apiDir>/src/app/function/<group>/` must be `<group>.crud[.<sub>...].spec.ts`
|
|
17437
|
+
* or `<group>.scenario[.<sub>...].spec.ts`. Drift forms surface a rename
|
|
17438
|
+
* suggestion derived from the shared `classifySpecFile` classifier in
|
|
17439
|
+
* `@dereekb/util`, so this rule and the `dbx_model_test_validate_app` MCP
|
|
17440
|
+
* tool never diverge.
|
|
17441
|
+
*
|
|
17442
|
+
* Not auto-fixable: renaming files (and updating any imports/snapshots they
|
|
17443
|
+
* carry) is outside the safe scope of an ESLint autofix.
|
|
17444
|
+
*/ var FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE = {
|
|
17445
|
+
meta: {
|
|
17446
|
+
type: 'suggestion',
|
|
17447
|
+
fixable: undefined,
|
|
17448
|
+
docs: {
|
|
17449
|
+
description: 'Require API spec filenames under `src/app/function/<group>/` to follow the `<group>.crud[.<sub>...].spec.ts` / `<group>.scenario[.<sub>...].spec.ts` convention.',
|
|
17450
|
+
recommended: true
|
|
17451
|
+
},
|
|
17452
|
+
messages: {
|
|
17453
|
+
testFileDriftRename: '`{{filename}}`: {{reason}} Rename to `{{recommendedRename}}`.',
|
|
17454
|
+
testFileMissingBucket: '`{{filename}}`: missing `crud` / `scenario` segment. Rename to `{{recommendedRename}}` (default) or to a `crud` variant if the tests are CRUD-flavored.',
|
|
17455
|
+
testFileNonGroupPlacement: '`{{filename}}`: first segment `{{group}}` does not match the parent folder `{{parentFolderName}}`. Move into `{{group}}/` or rename the prefix to match the current folder.'
|
|
17456
|
+
},
|
|
17457
|
+
schema: [
|
|
17458
|
+
{
|
|
17459
|
+
type: 'object',
|
|
17460
|
+
additionalProperties: false,
|
|
17461
|
+
properties: {
|
|
17462
|
+
functionDirSegment: {
|
|
17463
|
+
type: 'string'
|
|
17464
|
+
}
|
|
17465
|
+
}
|
|
17466
|
+
}
|
|
17467
|
+
]
|
|
17468
|
+
},
|
|
17469
|
+
create: function create(context) {
|
|
17470
|
+
var _context_options_, _options_functionDirSegment;
|
|
17471
|
+
var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
|
|
17472
|
+
var functionDirSegment = (_options_functionDirSegment = options.functionDirSegment) !== null && _options_functionDirSegment !== void 0 ? _options_functionDirSegment : DEFAULT_FUNCTION_DIR_SEGMENT;
|
|
17473
|
+
var matched = matchFunctionSpecPath(context.filename, functionDirSegment);
|
|
17474
|
+
function check(programNode) {
|
|
17475
|
+
if (!matched) return;
|
|
17476
|
+
var classification = classifySpecFile({
|
|
17477
|
+
filename: matched.filename,
|
|
17478
|
+
parentFolderName: matched.parentFolderName
|
|
17479
|
+
});
|
|
17480
|
+
if (classification.isCanonical) return;
|
|
17481
|
+
if (classification.kind === 'non-spec') return;
|
|
17482
|
+
if (classification.kind === 'crud-misplaced' || classification.kind === 'scenario-misplaced') {
|
|
17483
|
+
var _classification_driftReason, _classification_recommendedRename;
|
|
17484
|
+
context.report({
|
|
17485
|
+
node: programNode,
|
|
17486
|
+
messageId: 'testFileDriftRename',
|
|
17487
|
+
data: {
|
|
17488
|
+
filename: classification.filename,
|
|
17489
|
+
reason: (_classification_driftReason = classification.driftReason) !== null && _classification_driftReason !== void 0 ? _classification_driftReason : 'segment order does not match the convention.',
|
|
17490
|
+
recommendedRename: (_classification_recommendedRename = classification.recommendedRename) !== null && _classification_recommendedRename !== void 0 ? _classification_recommendedRename : ''
|
|
17491
|
+
}
|
|
17492
|
+
});
|
|
17493
|
+
} else if (classification.kind === 'no-bucket') {
|
|
17494
|
+
var _classification_recommendedRename1;
|
|
17495
|
+
context.report({
|
|
17496
|
+
node: programNode,
|
|
17497
|
+
messageId: 'testFileMissingBucket',
|
|
17498
|
+
data: {
|
|
17499
|
+
filename: classification.filename,
|
|
17500
|
+
recommendedRename: (_classification_recommendedRename1 = classification.recommendedRename) !== null && _classification_recommendedRename1 !== void 0 ? _classification_recommendedRename1 : ''
|
|
17501
|
+
}
|
|
17502
|
+
});
|
|
17503
|
+
} else if (classification.kind === 'non-group') {
|
|
17504
|
+
context.report({
|
|
17505
|
+
node: programNode,
|
|
17506
|
+
messageId: 'testFileNonGroupPlacement',
|
|
17507
|
+
data: {
|
|
17508
|
+
filename: classification.filename,
|
|
17509
|
+
group: classification.group,
|
|
17510
|
+
parentFolderName: matched.parentFolderName
|
|
17511
|
+
}
|
|
17512
|
+
});
|
|
17513
|
+
}
|
|
17514
|
+
}
|
|
17515
|
+
return {
|
|
17516
|
+
Program: function Program(node) {
|
|
17517
|
+
return check(node);
|
|
17518
|
+
}
|
|
17519
|
+
};
|
|
17520
|
+
}
|
|
17521
|
+
};
|
|
17522
|
+
|
|
17523
|
+
function isGroupIndex(filename, functionDirSegment) {
|
|
17524
|
+
var normalized = filename.split(sep).join('/');
|
|
17525
|
+
var marker = "/".concat(functionDirSegment, "/");
|
|
17526
|
+
var markerIdx = normalized.indexOf(marker);
|
|
17527
|
+
var result = false;
|
|
17528
|
+
if (markerIdx >= 0) {
|
|
17529
|
+
var afterMarker = normalized.slice(markerIdx + marker.length);
|
|
17530
|
+
var parts = afterMarker.split('/');
|
|
17531
|
+
result = parts.length === 2 && parts[1] === 'index.ts';
|
|
17532
|
+
}
|
|
17533
|
+
return result;
|
|
17534
|
+
}
|
|
17535
|
+
function hasCrudSpec(groupDir, group) {
|
|
17536
|
+
var found = false;
|
|
17537
|
+
try {
|
|
17538
|
+
var entries = readdirSync(groupDir);
|
|
17539
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
17540
|
+
try {
|
|
17541
|
+
for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
17542
|
+
var entry = _step.value;
|
|
17543
|
+
var classification = classifySpecFile({
|
|
17544
|
+
filename: entry,
|
|
17545
|
+
parentFolderName: group
|
|
17546
|
+
});
|
|
17547
|
+
if (classification.kind === 'crud' || classification.kind === 'crud-subgroup') {
|
|
17548
|
+
found = true;
|
|
17549
|
+
break;
|
|
17550
|
+
}
|
|
17551
|
+
}
|
|
17552
|
+
} catch (err) {
|
|
17553
|
+
_didIteratorError = true;
|
|
17554
|
+
_iteratorError = err;
|
|
17555
|
+
} finally{
|
|
17556
|
+
try {
|
|
17557
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
17558
|
+
_iterator.return();
|
|
17559
|
+
}
|
|
17560
|
+
} finally{
|
|
17561
|
+
if (_didIteratorError) {
|
|
17562
|
+
throw _iteratorError;
|
|
17563
|
+
}
|
|
17564
|
+
}
|
|
17565
|
+
}
|
|
17566
|
+
} catch (unused) {
|
|
17567
|
+
// Directory unreadable — treat as no crud spec; rule will emit the warning
|
|
17568
|
+
// and the user can investigate. We do NOT swallow the error silently
|
|
17569
|
+
// in any other way.
|
|
17570
|
+
}
|
|
17571
|
+
return found;
|
|
17572
|
+
}
|
|
17573
|
+
/**
|
|
17574
|
+
* ESLint rule that fires on every `<apiDir>/src/app/function/<group>/index.ts`
|
|
17575
|
+
* (the canonical anchor file for a Firebase Functions group) and verifies
|
|
17576
|
+
* that the same folder contains a `<group>.crud.spec.ts` (or any
|
|
17577
|
+
* `<group>.crud.<sub>.spec.ts` variant) sibling. Mirrors the coverage check
|
|
17578
|
+
* in `dbx_model_test_validate_app` so editor + CI lint flag missing CRUD
|
|
17579
|
+
* coverage without needing the MCP audit.
|
|
17580
|
+
*
|
|
17581
|
+
* Not auto-fixable: creating a spec file shell with sensible test cases is
|
|
17582
|
+
* outside the safe scope of an ESLint autofix.
|
|
17583
|
+
*/ var FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE = {
|
|
17584
|
+
meta: {
|
|
17585
|
+
type: 'suggestion',
|
|
17586
|
+
fixable: undefined,
|
|
17587
|
+
docs: {
|
|
17588
|
+
description: 'Require every API model-group function folder to have a `<group>.crud.spec.ts` covering its CRUD function map.',
|
|
17589
|
+
recommended: true
|
|
17590
|
+
},
|
|
17591
|
+
messages: {
|
|
17592
|
+
modelGroupMissingCrudSpec: 'Model group `{{group}}` has no `{{expectedFilename}}`. Add it covering the CRUD function map (create/read/update/delete + permission/error paths).'
|
|
17593
|
+
},
|
|
17594
|
+
schema: [
|
|
17595
|
+
{
|
|
17596
|
+
type: 'object',
|
|
17597
|
+
additionalProperties: false,
|
|
17598
|
+
properties: {
|
|
17599
|
+
functionDirSegment: {
|
|
17600
|
+
type: 'string'
|
|
17601
|
+
}
|
|
17602
|
+
}
|
|
17603
|
+
}
|
|
17604
|
+
]
|
|
17605
|
+
},
|
|
17606
|
+
create: function create(context) {
|
|
17607
|
+
var _context_options_, _options_functionDirSegment;
|
|
17608
|
+
var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
|
|
17609
|
+
var functionDirSegment = (_options_functionDirSegment = options.functionDirSegment) !== null && _options_functionDirSegment !== void 0 ? _options_functionDirSegment : DEFAULT_FUNCTION_DIR_SEGMENT;
|
|
17610
|
+
var filename = context.filename;
|
|
17611
|
+
var isAnchor = isGroupIndex(filename, functionDirSegment);
|
|
17612
|
+
function check(programNode) {
|
|
17613
|
+
if (!isAnchor) return;
|
|
17614
|
+
var groupDir = dirname(filename);
|
|
17615
|
+
var group = basename(groupDir);
|
|
17616
|
+
if (hasCrudSpec(groupDir, group)) return;
|
|
17617
|
+
var expectedFilename = buildCanonicalFilename({
|
|
17618
|
+
group: group,
|
|
17619
|
+
bucket: 'crud',
|
|
17620
|
+
subgroups: []
|
|
17621
|
+
});
|
|
17622
|
+
context.report({
|
|
17623
|
+
node: programNode,
|
|
17624
|
+
messageId: 'modelGroupMissingCrudSpec',
|
|
17625
|
+
data: {
|
|
17626
|
+
group: group,
|
|
17627
|
+
expectedFilename: expectedFilename
|
|
17628
|
+
}
|
|
17629
|
+
});
|
|
17630
|
+
}
|
|
17631
|
+
return {
|
|
17632
|
+
Program: function Program(node) {
|
|
17633
|
+
return check(node);
|
|
17634
|
+
}
|
|
17635
|
+
};
|
|
17636
|
+
}
|
|
17637
|
+
};
|
|
17638
|
+
|
|
17230
17639
|
/**
|
|
17231
17640
|
* ESLint plugin for `@dereekb/firebase` rules.
|
|
17232
17641
|
*
|
|
@@ -17246,7 +17655,9 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
|
|
|
17246
17655
|
'require-firestore-rule-for-service-model': FIREBASE_REQUIRE_FIRESTORE_RULE_FOR_SERVICE_MODEL_RULE,
|
|
17247
17656
|
'require-dbx-model-service-factory-tag': FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE,
|
|
17248
17657
|
'require-service-factory-for-dbx-model': FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE,
|
|
17249
|
-
'require-dbx-model-companion-tags': FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE
|
|
17658
|
+
'require-dbx-model-companion-tags': FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE,
|
|
17659
|
+
'require-canonical-api-spec-filename': FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE,
|
|
17660
|
+
'require-api-crud-spec-for-group': FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE
|
|
17250
17661
|
}
|
|
17251
17662
|
};
|
|
17252
17663
|
/**
|
|
@@ -17255,4 +17666,4 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
|
|
|
17255
17666
|
* @dbxAllowConstantName
|
|
17256
17667
|
*/ var firebaseESLintPlugin = FIREBASE_ESLINT_PLUGIN;
|
|
17257
17668
|
|
|
17258
|
-
export { API_DETAILS_IMPORT_MODULE, DBX_MODEL_FIREBASE_INDEX_MARKER, DBX_MODEL_SERVICE_FACTORY_TAG, DEFAULT_API_DETAILS_FACTORY_NAME, DEFAULT_CONSTRAINT_FACTORY_NAMES, DEFAULT_CRUD_FUNCTION_TYPE_VERBS, DEFAULT_CRUD_VERB_NAMES, DEFAULT_DISCOVERY_EXCLUDED_DIRS, DEFAULT_FACTORY_SEARCH_ROOTS, DEFAULT_FACTORY_TAG, DEFAULT_FIRESTORE_RULES_FILENAME, DEFAULT_IDENTITY_FACTORY_NAME, DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES, DEFAULT_MODEL_MARKER_TAG, DEFAULT_MODEL_SEARCH_ROOTS, DEFAULT_PAGINATION_CONSTRAINT_NAMES, DEFAULT_REGISTRY_FACTORY_CALL_NAME, DEFAULT_STORAGE_FILE_UPLOAD_POLICY_TYPE_NAME, DEFAULT_STORAGE_RULES_FILENAME, FIREBASE_ESLINT_PLUGIN, FIREBASE_MODEL_SERVICE_FACTORY_MODULE, FIREBASE_MODEL_SERVICE_FACTORY_NAME, FIREBASE_MODULE, FIREBASE_REQUIRE_API_DETAILS_FOR_CRUD_FUNCTION_RULE, FIREBASE_REQUIRE_COMPLETE_CRUD_FUNCTION_CONFIG_MAP_RULE, FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_QUERY_SUFFIX_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_VALID_DISPATCHER_RULE, FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE, FIREBASE_REQUIRE_FIRESTORE_CONSTRAINT_TYPE_PARAMETER_RULE, FIREBASE_REQUIRE_FIRESTORE_RULE_FOR_SERVICE_MODEL_RULE, FIREBASE_REQUIRE_INPUT_TYPE_FOR_API_DETAILS_RULE, FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE, FIREBASE_REQUIRE_STORAGEFILE_POLICY_MATCHES_RULES_RULE, FIREBASE_REQUIRE_TAGGED_FIRESTORE_CONSTRAINTS_RULE, INPUT_TYPE_PROPERTY_NAME, MIRRORS_POLICY_KEY_MARKER_REGEX, MODEL_FIREBASE_CRUD_FUNCTION_CONFIG_MAP_TYPE_NAME, QUERY_SUFFIX, discoveryGlobExcludeFilter, firebaseESLintPlugin, parseFirestoreRules, parseStorageRules };
|
|
17669
|
+
export { API_DETAILS_IMPORT_MODULE, DBX_MODEL_FIREBASE_INDEX_MARKER, DBX_MODEL_SERVICE_FACTORY_TAG, DEFAULT_API_DETAILS_FACTORY_NAME, DEFAULT_CONSTRAINT_FACTORY_NAMES, DEFAULT_CRUD_FUNCTION_TYPE_VERBS, DEFAULT_CRUD_VERB_NAMES, DEFAULT_DISCOVERY_EXCLUDED_DIRS, DEFAULT_FACTORY_SEARCH_ROOTS, DEFAULT_FACTORY_TAG, DEFAULT_FIRESTORE_RULES_FILENAME, DEFAULT_FUNCTION_DIR_SEGMENT, DEFAULT_IDENTITY_FACTORY_NAME, DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES, DEFAULT_MODEL_MARKER_TAG, DEFAULT_MODEL_SEARCH_ROOTS, DEFAULT_PAGINATION_CONSTRAINT_NAMES, DEFAULT_REGISTRY_FACTORY_CALL_NAME, DEFAULT_STORAGE_FILE_UPLOAD_POLICY_TYPE_NAME, DEFAULT_STORAGE_RULES_FILENAME, FIREBASE_ESLINT_PLUGIN, FIREBASE_MODEL_SERVICE_FACTORY_MODULE, FIREBASE_MODEL_SERVICE_FACTORY_NAME, FIREBASE_MODULE, FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE, FIREBASE_REQUIRE_API_DETAILS_FOR_CRUD_FUNCTION_RULE, FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE, FIREBASE_REQUIRE_COMPLETE_CRUD_FUNCTION_CONFIG_MAP_RULE, FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_QUERY_SUFFIX_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_VALID_DISPATCHER_RULE, FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE, FIREBASE_REQUIRE_FIRESTORE_CONSTRAINT_TYPE_PARAMETER_RULE, FIREBASE_REQUIRE_FIRESTORE_RULE_FOR_SERVICE_MODEL_RULE, FIREBASE_REQUIRE_INPUT_TYPE_FOR_API_DETAILS_RULE, FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE, FIREBASE_REQUIRE_STORAGEFILE_POLICY_MATCHES_RULES_RULE, FIREBASE_REQUIRE_TAGGED_FIRESTORE_CONSTRAINTS_RULE, INPUT_TYPE_PROPERTY_NAME, MIRRORS_POLICY_KEY_MARKER_REGEX, MODEL_FIREBASE_CRUD_FUNCTION_CONFIG_MAP_TYPE_NAME, QUERY_SUFFIX, discoveryGlobExcludeFilter, firebaseESLintPlugin, parseFirestoreRules, parseStorageRules };
|
package/eslint/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase/eslint",
|
|
3
|
-
"version": "13.12.
|
|
3
|
+
"version": "13.12.6",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@dereekb/util": "13.12.
|
|
5
|
+
"@dereekb/util": "13.12.6",
|
|
6
6
|
"@marcbachmann/cel-js": "^7.6.1",
|
|
7
7
|
"@typescript-eslint/parser": "8.59.3",
|
|
8
8
|
"@typescript-eslint/utils": "8.59.3"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@dereekb/firebase": "13.12.
|
|
11
|
+
"@dereekb/firebase": "13.12.6",
|
|
12
12
|
"eslint": "10.4.0",
|
|
13
13
|
"firebase": "^12.12.1"
|
|
14
14
|
},
|
|
@@ -11,6 +11,8 @@ export { FIREBASE_REQUIRE_FIRESTORE_RULE_FOR_SERVICE_MODEL_RULE, DEFAULT_FIRESTO
|
|
|
11
11
|
export { FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE, FIREBASE_MODEL_SERVICE_FACTORY_NAME, FIREBASE_MODEL_SERVICE_FACTORY_MODULE, DBX_MODEL_SERVICE_FACTORY_TAG, type FirebaseRequireDbxModelServiceFactoryTagRuleOptions, type FirebaseRequireDbxModelServiceFactoryTagRuleDefinition } from './require-dbx-model-service-factory-tag.rule';
|
|
12
12
|
export { FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE, DEFAULT_FACTORY_SEARCH_ROOTS, DEFAULT_MODEL_MARKER_TAG, DEFAULT_FACTORY_TAG, type FirebaseRequireServiceFactoryForDbxModelRuleOptions, type FirebaseRequireServiceFactoryForDbxModelRuleDefinition } from './require-service-factory-for-dbx-model.rule';
|
|
13
13
|
export { FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE, type FirebaseRequireDbxModelCompanionTagsRuleOptions, type FirebaseRequireDbxModelCompanionTagsRuleDefinition } from './require-dbx-model-companion-tags.rule';
|
|
14
|
+
export { FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE, DEFAULT_FUNCTION_DIR_SEGMENT, type FirebaseRequireCanonicalApiSpecFilenameRuleOptions, type FirebaseRequireCanonicalApiSpecFilenameRuleDefinition } from './require-canonical-api-spec-filename.rule';
|
|
15
|
+
export { FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE, type FirebaseRequireApiCrudSpecForGroupRuleOptions, type FirebaseRequireApiCrudSpecForGroupRuleDefinition } from './require-api-crud-spec-for-group.rule';
|
|
14
16
|
export { parseStorageRules, MIRRORS_POLICY_KEY_MARKER_REGEX, type ParsedRuleBranch, type ParsedStorageRulesBlock } from './storage-rules-parser';
|
|
15
17
|
export { parseFirestoreRules, type ParsedFirestoreMatchBlock } from './firestore-rules-parser';
|
|
16
18
|
export { FIREBASE_ESLINT_PLUGIN, firebaseESLintPlugin, type FirebaseEslintPlugin } from './plugin';
|
|
@@ -11,6 +11,8 @@ import { FIREBASE_REQUIRE_FIRESTORE_RULE_FOR_SERVICE_MODEL_RULE } from './requir
|
|
|
11
11
|
import { FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE } from './require-dbx-model-service-factory-tag.rule';
|
|
12
12
|
import { FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE } from './require-service-factory-for-dbx-model.rule';
|
|
13
13
|
import { FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE } from './require-dbx-model-companion-tags.rule';
|
|
14
|
+
import { FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE } from './require-canonical-api-spec-filename.rule';
|
|
15
|
+
import { FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE } from './require-api-crud-spec-for-group.rule';
|
|
14
16
|
/**
|
|
15
17
|
* ESLint plugin interface for `@dereekb/firebase` rules.
|
|
16
18
|
*/
|
|
@@ -29,6 +31,8 @@ export interface FirebaseEslintPlugin {
|
|
|
29
31
|
readonly 'require-dbx-model-service-factory-tag': typeof FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE;
|
|
30
32
|
readonly 'require-service-factory-for-dbx-model': typeof FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE;
|
|
31
33
|
readonly 'require-dbx-model-companion-tags': typeof FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE;
|
|
34
|
+
readonly 'require-canonical-api-spec-filename': typeof FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE;
|
|
35
|
+
readonly 'require-api-crud-spec-for-group': typeof FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE;
|
|
32
36
|
};
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type AstNode } from './util';
|
|
2
|
+
/**
|
|
3
|
+
* Options for the require-api-crud-spec-for-group rule.
|
|
4
|
+
*/
|
|
5
|
+
export interface FirebaseRequireApiCrudSpecForGroupRuleOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Path-suffix marker that anchors the rule to API function folders.
|
|
8
|
+
* Defaults to `'src/app/function'`. Files outside this segment are ignored.
|
|
9
|
+
*/
|
|
10
|
+
readonly functionDirSegment?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* ESLint rule definition for require-api-crud-spec-for-group.
|
|
14
|
+
*/
|
|
15
|
+
export interface FirebaseRequireApiCrudSpecForGroupRuleDefinition {
|
|
16
|
+
readonly meta: {
|
|
17
|
+
readonly type: 'suggestion';
|
|
18
|
+
readonly fixable: undefined;
|
|
19
|
+
readonly docs: {
|
|
20
|
+
readonly description: string;
|
|
21
|
+
readonly recommended: boolean;
|
|
22
|
+
};
|
|
23
|
+
readonly messages: Readonly<Record<string, string>>;
|
|
24
|
+
readonly schema: readonly object[];
|
|
25
|
+
};
|
|
26
|
+
create(context: {
|
|
27
|
+
options: FirebaseRequireApiCrudSpecForGroupRuleOptions[];
|
|
28
|
+
filename: string;
|
|
29
|
+
report: (descriptor: {
|
|
30
|
+
node: AstNode;
|
|
31
|
+
messageId: string;
|
|
32
|
+
data?: Record<string, string>;
|
|
33
|
+
}) => void;
|
|
34
|
+
}): Record<string, (node: AstNode) => void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ESLint rule that fires on every `<apiDir>/src/app/function/<group>/index.ts`
|
|
38
|
+
* (the canonical anchor file for a Firebase Functions group) and verifies
|
|
39
|
+
* that the same folder contains a `<group>.crud.spec.ts` (or any
|
|
40
|
+
* `<group>.crud.<sub>.spec.ts` variant) sibling. Mirrors the coverage check
|
|
41
|
+
* in `dbx_model_test_validate_app` so editor + CI lint flag missing CRUD
|
|
42
|
+
* coverage without needing the MCP audit.
|
|
43
|
+
*
|
|
44
|
+
* Not auto-fixable: creating a spec file shell with sensible test cases is
|
|
45
|
+
* outside the safe scope of an ESLint autofix.
|
|
46
|
+
*/
|
|
47
|
+
export declare const FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE: FirebaseRequireApiCrudSpecForGroupRuleDefinition;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type AstNode } from './util';
|
|
2
|
+
/**
|
|
3
|
+
* Default subpath segment (relative to an app source root) below which the
|
|
4
|
+
* rule expects model-group function folders. Matches the convention used by
|
|
5
|
+
* `<apiDir>/src/app/function/<group>/`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_FUNCTION_DIR_SEGMENT = "src/app/function";
|
|
8
|
+
/**
|
|
9
|
+
* Options for the require-canonical-api-spec-filename rule.
|
|
10
|
+
*/
|
|
11
|
+
export interface FirebaseRequireCanonicalApiSpecFilenameRuleOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Path-suffix marker that anchors the rule to API function folders.
|
|
14
|
+
* Defaults to `'src/app/function'`. Any `.spec.ts` whose path contains
|
|
15
|
+
* `<marker>/<group>/<filename>` is classified; everything else is ignored.
|
|
16
|
+
*/
|
|
17
|
+
readonly functionDirSegment?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* ESLint rule definition for require-canonical-api-spec-filename.
|
|
21
|
+
*/
|
|
22
|
+
export interface FirebaseRequireCanonicalApiSpecFilenameRuleDefinition {
|
|
23
|
+
readonly meta: {
|
|
24
|
+
readonly type: 'suggestion';
|
|
25
|
+
readonly fixable: undefined;
|
|
26
|
+
readonly docs: {
|
|
27
|
+
readonly description: string;
|
|
28
|
+
readonly recommended: boolean;
|
|
29
|
+
};
|
|
30
|
+
readonly messages: Readonly<Record<string, string>>;
|
|
31
|
+
readonly schema: readonly object[];
|
|
32
|
+
};
|
|
33
|
+
create(context: {
|
|
34
|
+
options: FirebaseRequireCanonicalApiSpecFilenameRuleOptions[];
|
|
35
|
+
filename: string;
|
|
36
|
+
report: (descriptor: {
|
|
37
|
+
node: AstNode;
|
|
38
|
+
messageId: string;
|
|
39
|
+
data?: Record<string, string>;
|
|
40
|
+
}) => void;
|
|
41
|
+
}): Record<string, (node: AstNode) => void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* ESLint rule that enforces the canonical naming convention for Firebase
|
|
45
|
+
* Functions API spec files: every `.spec.ts` under
|
|
46
|
+
* `<apiDir>/src/app/function/<group>/` must be `<group>.crud[.<sub>...].spec.ts`
|
|
47
|
+
* or `<group>.scenario[.<sub>...].spec.ts`. Drift forms surface a rename
|
|
48
|
+
* suggestion derived from the shared `classifySpecFile` classifier in
|
|
49
|
+
* `@dereekb/util`, so this rule and the `dbx_model_test_validate_app` MCP
|
|
50
|
+
* tool never diverge.
|
|
51
|
+
*
|
|
52
|
+
* Not auto-fixable: renaming files (and updating any imports/snapshots they
|
|
53
|
+
* carry) is outside the safe scope of an ESLint autofix.
|
|
54
|
+
*/
|
|
55
|
+
export declare const FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE: FirebaseRequireCanonicalApiSpecFilenameRuleDefinition;
|