@dereekb/dbx-cli 13.16.0 → 13.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.
- package/eslint/index.cjs.default.js +1 -0
- package/eslint/index.cjs.js +1050 -0
- package/eslint/index.cjs.mjs +2 -0
- package/eslint/index.d.ts +1 -0
- package/eslint/index.esm.js +1046 -0
- package/eslint/package.json +25 -0
- package/eslint/rollup.alias-internal.config.d.ts +11 -0
- package/eslint/src/index.d.ts +1 -0
- package/eslint/src/lib/index.d.ts +2 -0
- package/eslint/src/lib/plugin.d.ts +22 -0
- package/eslint/src/lib/valid-dbx-route-model-tags.rule.d.ts +59 -0
- package/firebase-api-manifest/main.js +177 -102
- package/firebase-api-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/main.js +8 -2
- package/generate-mcp-manifest/package.json +3 -3
- package/generate-route-manifest/main.js +1137 -0
- package/generate-route-manifest/package.json +10 -0
- package/index.cjs.js +4375 -1687
- package/index.esm.js +4355 -1688
- package/lint-cache/package.json +2 -2
- package/manifest-extract/index.cjs.js +54 -132
- package/manifest-extract/index.esm.js +53 -131
- package/manifest-extract/package.json +9 -4
- package/package.json +16 -6
- package/src/lib/index.d.ts +2 -0
- package/src/lib/manifest/types.d.ts +53 -0
- package/src/lib/mcp-scan/manifest/package-root.d.ts +17 -0
- package/src/lib/mcp-scan/manifest/tokens-schema.d.ts +5 -4
- package/src/lib/mcp-scan/scan/extract-models/assemble.d.ts +17 -0
- package/src/lib/route/component-resolve.d.ts +48 -0
- package/src/lib/route/index.d.ts +18 -0
- package/src/lib/route/route-build-tree.d.ts +31 -0
- package/src/lib/route/route-extract.d.ts +46 -0
- package/src/lib/route/route-load-tree.d.ts +17 -0
- package/src/lib/route/route-manifest.d.ts +132 -0
- package/src/lib/route/route-model-tag.d.ts +89 -0
- package/src/lib/route/route-models-extract.d.ts +22 -0
- package/src/lib/route/route-resolve-sources.d.ts +39 -0
- package/src/lib/route/route-types.d.ts +136 -0
- package/src/lib/route/url-match.d.ts +116 -0
- package/src/lib/scan-helpers/firestore-model-extract-utils.d.ts +43 -0
- package/test/package.json +9 -9
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dereekb/dbx-cli/eslint",
|
|
3
|
+
"version": "13.17.0",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@dereekb/dbx-cli": "13.17.0",
|
|
6
|
+
"@dereekb/util": "13.17.0",
|
|
7
|
+
"@typescript-eslint/utils": "8.59.3"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@typescript-eslint/parser": "8.59.3",
|
|
11
|
+
"eslint": "10.4.0"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
".": {
|
|
16
|
+
"module": "./index.esm.js",
|
|
17
|
+
"types": "./index.d.ts",
|
|
18
|
+
"import": "./index.cjs.mjs",
|
|
19
|
+
"default": "./index.cjs.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"module": "./index.esm.js",
|
|
23
|
+
"main": "./index.cjs.js",
|
|
24
|
+
"types": "./index.d.ts"
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param config - The rollup config @nx/rollup hands to the hook.
|
|
3
|
+
* @param _options - The @nx/rollup options (unused).
|
|
4
|
+
* @returns The mutated rollup config.
|
|
5
|
+
*
|
|
6
|
+
* @nx /rollup `rollupConfig` hook that inlines the workspace's internal `@dereekb/*` imports into
|
|
7
|
+
* the published ESLint plugin bundle. Prepends an alias plugin redirecting {@link INTERNAL_ALIASES}
|
|
8
|
+
* to their TS sources and wraps the `external: "all"` callback so {@link BUNDLED_DEPENDENCIES} are
|
|
9
|
+
* treated as internal.
|
|
10
|
+
*/
|
|
11
|
+
export default function applyInternalAliases(config: any, _options: any): Promise<any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type DbxCliValidDbxRouteModelTagsRuleDefinition } from './valid-dbx-route-model-tags.rule';
|
|
2
|
+
/**
|
|
3
|
+
* ESLint plugin interface for `@dereekb/dbx-cli` rules.
|
|
4
|
+
*/
|
|
5
|
+
export interface DbxCliEslintPlugin {
|
|
6
|
+
readonly rules: {
|
|
7
|
+
readonly 'valid-dbx-route-model-tags': DbxCliValidDbxRouteModelTagsRuleDefinition;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* ESLint plugin for `@dereekb/dbx-cli` rules.
|
|
12
|
+
*
|
|
13
|
+
* Register as a plugin in your flat ESLint config, then enable individual rules
|
|
14
|
+
* under the chosen plugin prefix (e.g. 'dereekb-dbx-cli/valid-dbx-route-model-tags').
|
|
15
|
+
*/
|
|
16
|
+
export declare const DBX_CLI_ESLINT_PLUGIN: DbxCliEslintPlugin;
|
|
17
|
+
/**
|
|
18
|
+
* camelCase alias of {@link DBX_CLI_ESLINT_PLUGIN} matching the conventional ESLint plugin export name.
|
|
19
|
+
*
|
|
20
|
+
* @dbxAllowConstantName
|
|
21
|
+
*/
|
|
22
|
+
export declare const dbxCliESLintPlugin: DbxCliEslintPlugin;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule that validates `@dbxRouteModel` / `@dbxRouteModelList` JSDoc tag
|
|
3
|
+
* grammar at author time. Each tag is parsed through `@dereekb/dbx-cli`'s
|
|
4
|
+
* canonical {@link parseRouteModelTag} grammar — the exact parser the build-time
|
|
5
|
+
* route-manifest builder uses — so ESLint and the manifest generator can never
|
|
6
|
+
* disagree about what a valid tag is. A malformed tag (typo'd model type, bad
|
|
7
|
+
* key template, wrong token count, unknown `@dbxRouteModel*` name) is reported on
|
|
8
|
+
* its own JSDoc line with the parser's failure reason.
|
|
9
|
+
*
|
|
10
|
+
* Scope: route-model tags live on `@Component` classes in `*.component.ts` and on
|
|
11
|
+
* exported `Ng2StateDeclaration` consts in `*.router.ts`, so the rule visits class
|
|
12
|
+
* and variable declarations (anchoring to their `export` wrapper for the leading
|
|
13
|
+
* JSDoc). It has no auto-fix — the correct value requires human / agent judgment.
|
|
14
|
+
*/
|
|
15
|
+
interface AstNode {
|
|
16
|
+
readonly type: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* ESLint rule definition for valid-dbx-route-model-tags.
|
|
21
|
+
*/
|
|
22
|
+
export interface DbxCliValidDbxRouteModelTagsRuleDefinition {
|
|
23
|
+
readonly meta: {
|
|
24
|
+
readonly type: 'problem';
|
|
25
|
+
readonly docs: {
|
|
26
|
+
readonly description: string;
|
|
27
|
+
readonly recommended: boolean;
|
|
28
|
+
};
|
|
29
|
+
readonly messages: Readonly<Record<string, string>>;
|
|
30
|
+
readonly schema: readonly object[];
|
|
31
|
+
};
|
|
32
|
+
create(context: {
|
|
33
|
+
report: (descriptor: {
|
|
34
|
+
loc?: AstNode;
|
|
35
|
+
node?: AstNode;
|
|
36
|
+
messageId: string;
|
|
37
|
+
data?: Record<string, string>;
|
|
38
|
+
}) => void;
|
|
39
|
+
sourceCode: AstNode;
|
|
40
|
+
}): Record<string, (node: AstNode) => void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* ESLint rule enforcing that `@dbxRouteModel` / `@dbxRouteModelList` tags parse
|
|
44
|
+
* cleanly through the canonical route-model grammar. Reuses
|
|
45
|
+
* {@link parseRouteModelTag} from `@dereekb/dbx-cli` so a tag that lints clean is
|
|
46
|
+
* a tag the build-time manifest builder will accept.
|
|
47
|
+
*
|
|
48
|
+
* Checks (delegated to the grammar parser):
|
|
49
|
+
*
|
|
50
|
+
* - `@dbxRouteModel <modelType> <keyTemplate>` has exactly two tokens with a valid
|
|
51
|
+
* model-type identifier and a parseable key template (`:param` / `{authUid}` /
|
|
52
|
+
* even-segment `gb/:id/...` form).
|
|
53
|
+
* - `@dbxRouteModelList <modelType>` has a single valid model-type token.
|
|
54
|
+
* - A `@dbxRouteModel*` tag with an unknown name is flagged.
|
|
55
|
+
*
|
|
56
|
+
* Report-only — no auto-fix, since the corrected value needs judgment.
|
|
57
|
+
*/
|
|
58
|
+
export declare const DBX_CLI_VALID_DBX_ROUTE_MODEL_TAGS_RULE: DbxCliValidDbxRouteModelTagsRuleDefinition;
|
|
59
|
+
export {};
|
|
@@ -469,14 +469,92 @@ function readJsDocSummary(node) {
|
|
|
469
469
|
return result;
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
// packages/util/src/lib/string/sort.ts
|
|
473
|
+
var compareStrings = (a, b) => a.localeCompare(b);
|
|
474
|
+
|
|
475
|
+
// packages/dbx-cli/src/lib/scan-helpers/firestore-model-extract-utils.ts
|
|
476
|
+
import { Node as Node3 } from "ts-morph";
|
|
477
|
+
var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
|
|
478
|
+
function parseFirestoreModelIdentityArgs(args) {
|
|
479
|
+
let result;
|
|
480
|
+
if (args.length === 1) {
|
|
481
|
+
const modelType = stringLiteralValue(args[0]);
|
|
482
|
+
if (modelType !== void 0) {
|
|
483
|
+
result = { modelType, collectionPrefix: void 0, parentIdentityConst: void 0 };
|
|
484
|
+
}
|
|
485
|
+
} else if (args.length === 2) {
|
|
486
|
+
const first = stringLiteralValue(args[0]);
|
|
487
|
+
if (first === void 0) {
|
|
488
|
+
const modelType = stringLiteralValue(args[1]);
|
|
489
|
+
if (modelType !== void 0) {
|
|
490
|
+
result = { modelType, collectionPrefix: void 0, parentIdentityConst: identifierName(args[0]) };
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
result = { modelType: first, collectionPrefix: stringLiteralValue(args[1]), parentIdentityConst: void 0 };
|
|
494
|
+
}
|
|
495
|
+
} else if (args.length >= 3) {
|
|
496
|
+
const modelType = stringLiteralValue(args[1]);
|
|
497
|
+
if (modelType !== void 0) {
|
|
498
|
+
result = { modelType, collectionPrefix: stringLiteralValue(args[2]), parentIdentityConst: identifierName(args[0]) };
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
function resolveExtendsName(expr) {
|
|
504
|
+
const head = expr.getExpression().getText();
|
|
505
|
+
let result = head;
|
|
506
|
+
if (PASSTHROUGH_TYPE_WRAPPERS.has(head)) {
|
|
507
|
+
const typeArgs = expr.getTypeArguments();
|
|
508
|
+
if (typeArgs.length > 0) {
|
|
509
|
+
const peeled = peelTypeNode(typeArgs[0]);
|
|
510
|
+
if (peeled !== void 0) {
|
|
511
|
+
result = peeled;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
function peelTypeNode(node) {
|
|
518
|
+
let current = node;
|
|
519
|
+
while (Node3.isParenthesizedTypeNode(current)) {
|
|
520
|
+
current = current.getTypeNode();
|
|
521
|
+
}
|
|
522
|
+
let result;
|
|
523
|
+
if (Node3.isTypeReference(current)) {
|
|
524
|
+
const name = current.getTypeName().getText();
|
|
525
|
+
if (PASSTHROUGH_TYPE_WRAPPERS.has(name)) {
|
|
526
|
+
const inner = current.getTypeArguments();
|
|
527
|
+
if (inner.length > 0) {
|
|
528
|
+
result = peelTypeNode(inner[0]);
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
result = name;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
function stringLiteralValue(node) {
|
|
537
|
+
let result;
|
|
538
|
+
if (Node3.isStringLiteral(node) || Node3.isNoSubstitutionTemplateLiteral(node)) {
|
|
539
|
+
result = node.getLiteralText();
|
|
540
|
+
}
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
function identifierName(node) {
|
|
544
|
+
let result;
|
|
545
|
+
if (Node3.isIdentifier(node)) {
|
|
546
|
+
result = node.getText();
|
|
547
|
+
}
|
|
548
|
+
return result;
|
|
549
|
+
}
|
|
550
|
+
|
|
472
551
|
// packages/dbx-cli/manifest-extract/src/lib/extract-models.ts
|
|
473
|
-
import { Node as
|
|
552
|
+
import { Node as Node4, Project as Project3 } from "ts-morph";
|
|
474
553
|
var READ_LEVEL_VALUES = /* @__PURE__ */ new Set(["system", "owner", "admin-only", "permissions"]);
|
|
475
554
|
var SERVICE_FACTORY_TAG = "dbxModelServiceFactory";
|
|
476
555
|
var MCP_TOOL_NAME_SEGMENT_TAG = "dbxModelMcpToolNameSegment";
|
|
477
556
|
var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
|
|
478
557
|
var TOOL_NAME_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_$]*$/;
|
|
479
|
-
var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
|
|
480
558
|
var IDENTITY_FN = "firestoreModelIdentity";
|
|
481
559
|
var CONVERTER_FN_NAMES = ["snapshotConverterFunctions", "firestoreSubObject", "firestoreObjectArray"];
|
|
482
560
|
var SUB_OBJECT_FN = "firestoreSubObject";
|
|
@@ -484,6 +562,7 @@ var OBJECT_ARRAY_FN = "firestoreObjectArray";
|
|
|
484
562
|
var SNAPSHOT_FN = "snapshotConverterFunctions";
|
|
485
563
|
var FIELDS_LITERAL_KEY = "fields";
|
|
486
564
|
var OBJECT_FIELD_KEY = "objectField";
|
|
565
|
+
var FIRESTORE_FIELD_KEY = "firestoreField";
|
|
487
566
|
function extractModelsFromSource(input) {
|
|
488
567
|
const project = new Project3({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
|
|
489
568
|
const sourceFile = project.createSourceFile(input.name, input.text, { overwrite: true });
|
|
@@ -501,9 +580,9 @@ function readIdentities(sourceFile) {
|
|
|
501
580
|
if (!statement.isExported()) continue;
|
|
502
581
|
for (const decl of statement.getDeclarations()) {
|
|
503
582
|
const initializer = decl.getInitializer();
|
|
504
|
-
if (!initializer || !
|
|
583
|
+
if (!initializer || !Node4.isCallExpression(initializer)) continue;
|
|
505
584
|
if (initializer.getExpression().getText() !== IDENTITY_FN) continue;
|
|
506
|
-
const parsed =
|
|
585
|
+
const parsed = parseFirestoreModelIdentityArgs(initializer.getArguments());
|
|
507
586
|
if (parsed) {
|
|
508
587
|
out.push({ identityConst: decl.getName(), ...parsed });
|
|
509
588
|
}
|
|
@@ -511,32 +590,6 @@ function readIdentities(sourceFile) {
|
|
|
511
590
|
}
|
|
512
591
|
return out;
|
|
513
592
|
}
|
|
514
|
-
function parseIdentityArgs(call) {
|
|
515
|
-
const args = call.getArguments();
|
|
516
|
-
let result;
|
|
517
|
-
if (args.length === 1) {
|
|
518
|
-
const modelType = stringLiteralValue(args[0]);
|
|
519
|
-
if (modelType !== void 0) {
|
|
520
|
-
result = { modelType, collectionPrefix: void 0, parentIdentityConst: void 0 };
|
|
521
|
-
}
|
|
522
|
-
} else if (args.length === 2) {
|
|
523
|
-
const first = stringLiteralValue(args[0]);
|
|
524
|
-
if (first === void 0) {
|
|
525
|
-
const modelType = stringLiteralValue(args[1]);
|
|
526
|
-
if (modelType !== void 0) {
|
|
527
|
-
result = { modelType, collectionPrefix: void 0, parentIdentityConst: identifierName(args[0]) };
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
result = { modelType: first, collectionPrefix: stringLiteralValue(args[1]), parentIdentityConst: void 0 };
|
|
531
|
-
}
|
|
532
|
-
} else if (args.length >= 3) {
|
|
533
|
-
const modelType = stringLiteralValue(args[1]);
|
|
534
|
-
if (modelType !== void 0) {
|
|
535
|
-
result = { modelType, collectionPrefix: stringLiteralValue(args[2]), parentIdentityConst: identifierName(args[0]) };
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return result;
|
|
539
|
-
}
|
|
540
593
|
function readInterfaces(sourceFile) {
|
|
541
594
|
const out = [];
|
|
542
595
|
for (const decl of sourceFile.getInterfaces()) {
|
|
@@ -637,46 +690,13 @@ function readServiceFactoryModelType(jsDocs) {
|
|
|
637
690
|
}
|
|
638
691
|
return result;
|
|
639
692
|
}
|
|
640
|
-
function resolveExtendsName(expr) {
|
|
641
|
-
const head = expr.getExpression().getText();
|
|
642
|
-
let result = head;
|
|
643
|
-
if (PASSTHROUGH_TYPE_WRAPPERS.has(head)) {
|
|
644
|
-
const typeArgs = expr.getTypeArguments();
|
|
645
|
-
if (typeArgs.length > 0) {
|
|
646
|
-
const peeled = peelTypeNode(typeArgs[0]);
|
|
647
|
-
if (peeled !== void 0) {
|
|
648
|
-
result = peeled;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return result;
|
|
653
|
-
}
|
|
654
|
-
function peelTypeNode(node) {
|
|
655
|
-
let current = node;
|
|
656
|
-
while (Node3.isParenthesizedTypeNode(current)) {
|
|
657
|
-
current = current.getTypeNode();
|
|
658
|
-
}
|
|
659
|
-
let result;
|
|
660
|
-
if (Node3.isTypeReference(current)) {
|
|
661
|
-
const name = current.getTypeName().getText();
|
|
662
|
-
if (PASSTHROUGH_TYPE_WRAPPERS.has(name)) {
|
|
663
|
-
const inner = current.getTypeArguments();
|
|
664
|
-
if (inner.length > 0) {
|
|
665
|
-
result = peelTypeNode(inner[0]);
|
|
666
|
-
}
|
|
667
|
-
} else {
|
|
668
|
-
result = name;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
return result;
|
|
672
|
-
}
|
|
673
693
|
function readConverters(sourceFile) {
|
|
674
694
|
const out = [];
|
|
675
695
|
for (const statement of sourceFile.getVariableStatements()) {
|
|
676
696
|
if (!statement.isExported()) continue;
|
|
677
697
|
for (const decl of statement.getDeclarations()) {
|
|
678
698
|
const initializer = decl.getInitializer();
|
|
679
|
-
if (!initializer || !
|
|
699
|
+
if (!initializer || !Node4.isCallExpression(initializer)) continue;
|
|
680
700
|
const factory = initializer.getExpression().getText();
|
|
681
701
|
if (!isConverterFactoryName(factory)) continue;
|
|
682
702
|
const interfaceName = readGenericInterfaceName(initializer);
|
|
@@ -710,13 +730,13 @@ function readConverterFields(call) {
|
|
|
710
730
|
let result;
|
|
711
731
|
if (args.length > 0) {
|
|
712
732
|
const config = args[0];
|
|
713
|
-
if (
|
|
733
|
+
if (Node4.isObjectLiteralExpression(config)) {
|
|
714
734
|
let fieldsLiteral;
|
|
715
735
|
if (fnName === SNAPSHOT_FN) {
|
|
716
736
|
fieldsLiteral = readObjectProperty(config, FIELDS_LITERAL_KEY);
|
|
717
737
|
} else {
|
|
718
738
|
const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
|
|
719
|
-
if (objectField &&
|
|
739
|
+
if (objectField && Node4.isObjectLiteralExpression(objectField)) {
|
|
720
740
|
fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
|
|
721
741
|
}
|
|
722
742
|
}
|
|
@@ -730,7 +750,7 @@ function readConverterFields(call) {
|
|
|
730
750
|
function readFieldEntries(fields) {
|
|
731
751
|
const out = [];
|
|
732
752
|
for (const property of fields.getProperties()) {
|
|
733
|
-
if (
|
|
753
|
+
if (Node4.isPropertyAssignment(property)) {
|
|
734
754
|
const initializer = property.getInitializer();
|
|
735
755
|
const converterText = initializer ? initializer.getText().replaceAll(/\s+/g, " ").trim() : "";
|
|
736
756
|
const nested = initializer ? readNestedFromExpression(initializer) : void 0;
|
|
@@ -741,7 +761,7 @@ function readFieldEntries(fields) {
|
|
|
741
761
|
nestedConverterInline: nested?.inline,
|
|
742
762
|
nestedIsArray: nested?.isArray
|
|
743
763
|
});
|
|
744
|
-
} else if (
|
|
764
|
+
} else if (Node4.isShorthandPropertyAssignment(property)) {
|
|
745
765
|
const name = property.getName();
|
|
746
766
|
out.push({ key: name, converter: name });
|
|
747
767
|
}
|
|
@@ -750,7 +770,7 @@ function readFieldEntries(fields) {
|
|
|
750
770
|
}
|
|
751
771
|
function readNestedFromExpression(expr) {
|
|
752
772
|
let result;
|
|
753
|
-
if (
|
|
773
|
+
if (Node4.isCallExpression(expr)) {
|
|
754
774
|
const fnName = expr.getExpression().getText();
|
|
755
775
|
if (fnName === SUB_OBJECT_FN || fnName === OBJECT_ARRAY_FN) {
|
|
756
776
|
result = readNestedConverterCall(expr, fnName);
|
|
@@ -763,10 +783,10 @@ function readNestedConverterCall(call, fnName) {
|
|
|
763
783
|
const args = call.getArguments();
|
|
764
784
|
if (args.length > 0) {
|
|
765
785
|
const config = args[0];
|
|
766
|
-
if (
|
|
767
|
-
const
|
|
768
|
-
if (
|
|
769
|
-
result = buildNestedConverterMatch({ objectField, call, fnName });
|
|
786
|
+
if (Node4.isObjectLiteralExpression(config)) {
|
|
787
|
+
const valueNode = readPropertyValue(config, OBJECT_FIELD_KEY) ?? readPropertyValue(config, FIRESTORE_FIELD_KEY);
|
|
788
|
+
if (valueNode) {
|
|
789
|
+
result = buildNestedConverterMatch({ objectField: valueNode, call, fnName });
|
|
770
790
|
}
|
|
771
791
|
}
|
|
772
792
|
}
|
|
@@ -776,9 +796,9 @@ function buildNestedConverterMatch(input) {
|
|
|
776
796
|
const { objectField, call, fnName } = input;
|
|
777
797
|
const isArray = fnName === OBJECT_ARRAY_FN;
|
|
778
798
|
let result;
|
|
779
|
-
if (
|
|
799
|
+
if (Node4.isIdentifier(objectField)) {
|
|
780
800
|
result = { ref: objectField.getText(), isArray };
|
|
781
|
-
} else if (
|
|
801
|
+
} else if (Node4.isObjectLiteralExpression(objectField)) {
|
|
782
802
|
const fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
|
|
783
803
|
if (fieldsLiteral) {
|
|
784
804
|
result = {
|
|
@@ -792,22 +812,27 @@ function buildNestedConverterMatch(input) {
|
|
|
792
812
|
isArray
|
|
793
813
|
};
|
|
794
814
|
}
|
|
815
|
+
} else if (Node4.isCallExpression(objectField)) {
|
|
816
|
+
const nested = readNestedFromExpression(objectField);
|
|
817
|
+
if (nested) {
|
|
818
|
+
result = { ...nested, isArray };
|
|
819
|
+
}
|
|
795
820
|
}
|
|
796
821
|
return result;
|
|
797
822
|
}
|
|
798
823
|
function readPropertyValue(literal, key) {
|
|
799
824
|
const property = literal.getProperty(key);
|
|
800
825
|
let result;
|
|
801
|
-
if (property &&
|
|
826
|
+
if (property && Node4.isPropertyAssignment(property)) {
|
|
802
827
|
result = property.getInitializer();
|
|
803
|
-
} else if (property &&
|
|
828
|
+
} else if (property && Node4.isShorthandPropertyAssignment(property)) {
|
|
804
829
|
result = property.getNameNode();
|
|
805
830
|
}
|
|
806
831
|
return result;
|
|
807
832
|
}
|
|
808
833
|
function readObjectProperty(literal, key) {
|
|
809
834
|
const value = readPropertyValue(literal, key);
|
|
810
|
-
return value &&
|
|
835
|
+
return value && Node4.isObjectLiteralExpression(value) ? value : void 0;
|
|
811
836
|
}
|
|
812
837
|
function readEnums(sourceFile) {
|
|
813
838
|
const out = [];
|
|
@@ -906,20 +931,6 @@ function firstParagraph(text) {
|
|
|
906
931
|
}
|
|
907
932
|
return collected.join(" ").trim();
|
|
908
933
|
}
|
|
909
|
-
function stringLiteralValue(node) {
|
|
910
|
-
let result;
|
|
911
|
-
if (Node3.isStringLiteral(node) || Node3.isNoSubstitutionTemplateLiteral(node)) {
|
|
912
|
-
result = node.getLiteralText();
|
|
913
|
-
}
|
|
914
|
-
return result;
|
|
915
|
-
}
|
|
916
|
-
function identifierName(node) {
|
|
917
|
-
let result;
|
|
918
|
-
if (Node3.isIdentifier(node)) {
|
|
919
|
-
result = node.getText();
|
|
920
|
-
}
|
|
921
|
-
return result;
|
|
922
|
-
}
|
|
923
934
|
|
|
924
935
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-api-files.ts
|
|
925
936
|
function findApiFiles(packageRoot) {
|
|
@@ -1022,6 +1033,47 @@ function assembleModels(input) {
|
|
|
1022
1033
|
accumulator.entries.sort((a, b) => a.modelType.localeCompare(b.modelType));
|
|
1023
1034
|
return accumulator.entries;
|
|
1024
1035
|
}
|
|
1036
|
+
function collectModelEnums(input) {
|
|
1037
|
+
const registry = buildEnumRegistry(input.extractions);
|
|
1038
|
+
const referenced = collectReferencedEnumNames(input.models);
|
|
1039
|
+
const out = {};
|
|
1040
|
+
for (const name of [...referenced].sort((a, b) => a.localeCompare(b))) {
|
|
1041
|
+
const extracted = registry.get(name);
|
|
1042
|
+
if (extracted) {
|
|
1043
|
+
out[name] = buildModelEnumEntry(extracted);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return out;
|
|
1047
|
+
}
|
|
1048
|
+
function buildEnumRegistry(extractions) {
|
|
1049
|
+
const registry = /* @__PURE__ */ new Map();
|
|
1050
|
+
for (const { extraction } of extractions) {
|
|
1051
|
+
for (const e of extraction.enums) {
|
|
1052
|
+
if (!registry.has(e.name)) registry.set(e.name, e);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return registry;
|
|
1056
|
+
}
|
|
1057
|
+
function collectReferencedEnumNames(models) {
|
|
1058
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1059
|
+
for (const model of models) {
|
|
1060
|
+
addReferencedEnumNames(model.fields, referenced);
|
|
1061
|
+
}
|
|
1062
|
+
return referenced;
|
|
1063
|
+
}
|
|
1064
|
+
function addReferencedEnumNames(fields, referenced) {
|
|
1065
|
+
for (const field of fields) {
|
|
1066
|
+
if (field.enumRef) referenced.add(field.enumRef);
|
|
1067
|
+
if (field.nestedFields) addReferencedEnumNames(field.nestedFields, referenced);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function buildModelEnumEntry(e) {
|
|
1071
|
+
return {
|
|
1072
|
+
name: e.name,
|
|
1073
|
+
values: e.values.map((v) => v.description ? { name: v.name, value: v.value, description: v.description } : { name: v.name, value: v.value }),
|
|
1074
|
+
...e.description ? { description: e.description } : {}
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1025
1077
|
function buildGlobalRegistries(extractions) {
|
|
1026
1078
|
const converterRegistry = /* @__PURE__ */ new Map();
|
|
1027
1079
|
const interfaceRegistry = /* @__PURE__ */ new Map();
|
|
@@ -1358,13 +1410,8 @@ function escapeRegExp(value) {
|
|
|
1358
1410
|
|
|
1359
1411
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/emit.ts
|
|
1360
1412
|
import { format, resolveConfig } from "prettier";
|
|
1361
|
-
|
|
1362
|
-
// packages/util/src/lib/string/sort.ts
|
|
1363
|
-
var compareStrings = (a, b) => a.localeCompare(b);
|
|
1364
|
-
|
|
1365
|
-
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/emit.ts
|
|
1366
1413
|
async function renderManifest(input) {
|
|
1367
|
-
const { outputFile, entries, projectName, namespace, modelEntries, modelNamespace, emitConverters = false } = input;
|
|
1414
|
+
const { outputFile, entries, projectName, namespace, modelEntries, modelNamespace, enumEntries, enumNamespace, emitConverters = false } = input;
|
|
1368
1415
|
const importsByPackage = /* @__PURE__ */ new Map();
|
|
1369
1416
|
for (const entry of entries) {
|
|
1370
1417
|
if (!entry.packageName || !entry.validatorName) continue;
|
|
@@ -1378,12 +1425,18 @@ async function renderManifest(input) {
|
|
|
1378
1425
|
});
|
|
1379
1426
|
const entryLines = entries.map((e) => renderEntry(e));
|
|
1380
1427
|
const emitModels = Boolean(modelEntries && modelEntries.length > 0 && modelNamespace);
|
|
1381
|
-
const
|
|
1428
|
+
const emitEnums = Boolean(enumEntries && Object.keys(enumEntries).length > 0 && enumNamespace);
|
|
1429
|
+
const dbxCliTypeNames = ["type CliApiManifest", ...emitModels ? ["type CliModelManifest"] : [], ...emitEnums ? ["type CliEnumManifest"] : []];
|
|
1430
|
+
const dbxCliTypeImports = `import { ${dbxCliTypeNames.join(", ")} } from '@dereekb/dbx-cli';`;
|
|
1382
1431
|
const modelSection = emitModels ? `
|
|
1383
1432
|
|
|
1384
1433
|
export const ${modelNamespace}: CliModelManifest = [
|
|
1385
1434
|
${(modelEntries ?? []).map((m) => renderModelEntry(m, emitConverters)).join(",\n")}
|
|
1386
1435
|
];
|
|
1436
|
+
` : "";
|
|
1437
|
+
const enumSection = emitEnums ? `
|
|
1438
|
+
|
|
1439
|
+
export const ${enumNamespace}: CliEnumManifest = ${renderEnumManifest(enumEntries ?? {})};
|
|
1387
1440
|
` : "";
|
|
1388
1441
|
const source = `/* eslint-disable @nx/enforce-module-boundaries */
|
|
1389
1442
|
// AUTO-GENERATED \u2014 DO NOT EDIT.
|
|
@@ -1394,7 +1447,7 @@ ${dbxCliTypeImports}
|
|
|
1394
1447
|
|
|
1395
1448
|
export const ${namespace}: CliApiManifest = [
|
|
1396
1449
|
${entryLines.join(",\n")}
|
|
1397
|
-
];${modelSection}
|
|
1450
|
+
];${modelSection}${enumSection}
|
|
1398
1451
|
`;
|
|
1399
1452
|
return formatWithPrettier(source, outputFile);
|
|
1400
1453
|
}
|
|
@@ -1459,6 +1512,23 @@ function renderModelFields(fields, emitConverters) {
|
|
|
1459
1512
|
}
|
|
1460
1513
|
return result;
|
|
1461
1514
|
}
|
|
1515
|
+
function renderEnumManifest(enums) {
|
|
1516
|
+
const names = Object.keys(enums).sort((a, b) => a.localeCompare(b));
|
|
1517
|
+
const items = names.map((name) => `${JSON.stringify(name)}: ${renderEnumEntry(enums[name])}`);
|
|
1518
|
+
return names.length === 0 ? "{}" : `{ ${items.join(", ")} }`;
|
|
1519
|
+
}
|
|
1520
|
+
function renderEnumEntry(entry) {
|
|
1521
|
+
const parts = [`name: ${JSON.stringify(entry.name)}`, `values: ${renderEnumValues(entry.values)}`, entry.description ? `description: ${JSON.stringify(entry.description)}` : void 0];
|
|
1522
|
+
return `{ ${parts.filter(Boolean).join(", ")} }`;
|
|
1523
|
+
}
|
|
1524
|
+
function renderEnumValues(values) {
|
|
1525
|
+
const items = values.map((value) => renderEnumValue(value));
|
|
1526
|
+
return `[${items.join(", ")}]`;
|
|
1527
|
+
}
|
|
1528
|
+
function renderEnumValue(value) {
|
|
1529
|
+
const parts = [`name: ${JSON.stringify(value.name)}`, `value: ${JSON.stringify(value.value)}`, value.description ? `description: ${JSON.stringify(value.description)}` : void 0];
|
|
1530
|
+
return `{ ${parts.filter(Boolean).join(", ")} }`;
|
|
1531
|
+
}
|
|
1462
1532
|
function renderModelField(field, emitConverters) {
|
|
1463
1533
|
const nestedIsArrayLiteral = field.nestedIsArray ? "true" : "false";
|
|
1464
1534
|
const parts = [
|
|
@@ -1566,8 +1636,9 @@ async function main() {
|
|
|
1566
1636
|
collected.sort(compareEntries);
|
|
1567
1637
|
const modelEntries = flags.emitModels ? assembleModels({ extractions: modelSources }) : [];
|
|
1568
1638
|
const filteredModelEntries = flags.only ? modelEntries.filter((m) => flags.only?.has(m.modelType)) : modelEntries;
|
|
1639
|
+
const enumEntries = flags.emitModels ? collectModelEnums({ extractions: modelSources, models: filteredModelEntries }) : {};
|
|
1569
1640
|
ensureOutputDir(outputDir);
|
|
1570
|
-
const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace, modelEntries: filteredModelEntries, modelNamespace: deriveModelNamespace(flags.project), emitConverters: flags.emitModelConverters });
|
|
1641
|
+
const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace, modelEntries: filteredModelEntries, modelNamespace: deriveModelNamespace(flags.project), enumEntries, enumNamespace: deriveEnumNamespace(flags.project), emitConverters: flags.emitModelConverters });
|
|
1571
1642
|
if (existsSync3(outputFile) && readFileSync6(outputFile, "utf8") === formatted) {
|
|
1572
1643
|
console.log(`[unchanged] ${relative2(WORKSPACE_ROOT, outputFile)}`);
|
|
1573
1644
|
} else {
|
|
@@ -1575,7 +1646,7 @@ async function main() {
|
|
|
1575
1646
|
console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputFile)}`);
|
|
1576
1647
|
}
|
|
1577
1648
|
const groupCount = packageCache.size === 0 ? 0 : new Set(collected.map((c) => c.entry.groupName)).size;
|
|
1578
|
-
const modelSummary = flags.emitModels ? ` \xB7 ${filteredModelEntries.length} models` : "";
|
|
1649
|
+
const modelSummary = flags.emitModels ? ` \xB7 ${filteredModelEntries.length} models \xB7 ${Object.keys(enumEntries).length} enums` : "";
|
|
1579
1650
|
console.log(`Summary: ${groupCount} groups \xB7 ${collected.length} entries \xB7 ${collected.length - missingValidators} validators bound \xB7 ${missingValidators} missing \xB7 ${skippedGroups} skipped${modelSummary}`);
|
|
1580
1651
|
if (flags.strict && missingValidators > 0) {
|
|
1581
1652
|
console.error(`[strict] ${missingValidators} validator(s) missing \u2014 failing build.`);
|
|
@@ -1607,6 +1678,10 @@ function deriveModelNamespace(projectName) {
|
|
|
1607
1678
|
const base = (projectName ?? "cli").replaceAll(/[^a-zA-Z0-9]+/g, "_");
|
|
1608
1679
|
return `${base.toUpperCase()}_MODEL_MANIFEST`;
|
|
1609
1680
|
}
|
|
1681
|
+
function deriveEnumNamespace(projectName) {
|
|
1682
|
+
const base = (projectName ?? "cli").replaceAll(/[^a-zA-Z0-9]+/g, "_");
|
|
1683
|
+
return `${base.toUpperCase()}_ENUM_MANIFEST`;
|
|
1684
|
+
}
|
|
1610
1685
|
function parseFlags(argv) {
|
|
1611
1686
|
let only;
|
|
1612
1687
|
let strict = false;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firebase-api-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.17.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.17.0",
|
|
11
|
+
"@dereekb/util": "13.17.0",
|
|
12
12
|
"prettier": "3.8.3"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -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.17.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.17.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.17.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.17.0"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -1022,11 +1022,13 @@ function renderMcpManifest(input, now = /* @__PURE__ */ new Date()) {
|
|
|
1022
1022
|
registerToolEntry({ entry, segments, nameCounts, tools, seenNames, warnings, errors });
|
|
1023
1023
|
}
|
|
1024
1024
|
const models = input.modelManifest != null && input.modelManifest.length > 0 ? input.modelManifest.map(projectModelEntry) : void 0;
|
|
1025
|
+
const enums = input.enumManifest != null && Object.keys(input.enumManifest).length > 0 ? input.enumManifest : void 0;
|
|
1025
1026
|
const auth = input.auth == null ? void 0 : projectAuthSection(input.auth.registry, input.auth.app);
|
|
1026
1027
|
const base = { version: MCP_MANIFEST_VERSION, generatedAt: now.toISOString(), tools };
|
|
1027
1028
|
const manifest = {
|
|
1028
1029
|
...base,
|
|
1029
1030
|
...models == null ? {} : { models },
|
|
1031
|
+
...enums == null ? {} : { enums },
|
|
1030
1032
|
...auth == null ? {} : { auth }
|
|
1031
1033
|
};
|
|
1032
1034
|
return { manifest, warnings, errors };
|
|
@@ -1312,8 +1314,9 @@ Expected output of \`nx run <cli>:generate-api-manifest\`.`);
|
|
|
1312
1314
|
writeFileSync(tmpPath, serialized);
|
|
1313
1315
|
renameSync(tmpPath, outputPath);
|
|
1314
1316
|
const modelCount = manifest.models?.length ?? 0;
|
|
1317
|
+
const enumCount = manifest.enums == null ? 0 : Object.keys(manifest.enums).length;
|
|
1315
1318
|
const authCount = manifest.auth?.claims.length ?? 0;
|
|
1316
|
-
console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputPath)} \u2014 ${Object.keys(manifest.tools).length} tools, ${modelCount} models, ${authCount} auth claims`);
|
|
1319
|
+
console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputPath)} \u2014 ${Object.keys(manifest.tools).length} tools, ${modelCount} models, ${enumCount} enums, ${authCount} auth claims`);
|
|
1317
1320
|
}
|
|
1318
1321
|
async function maybeLoadAuth(flags) {
|
|
1319
1322
|
if (flags.app == null || flags.claimsInputs.length === 0) {
|
|
@@ -1342,7 +1345,10 @@ async function loadManifest(path) {
|
|
|
1342
1345
|
const namedModel = Object.entries(loaded).find(([key]) => key.endsWith("_MODEL_MANIFEST"));
|
|
1343
1346
|
const modelManifestValue = namedModel?.[1];
|
|
1344
1347
|
const modelManifest = Array.isArray(modelManifestValue) ? modelManifestValue : void 0;
|
|
1345
|
-
|
|
1348
|
+
const namedEnum = Object.entries(loaded).find(([key]) => key.endsWith("_ENUM_MANIFEST"));
|
|
1349
|
+
const enumManifestValue = namedEnum?.[1];
|
|
1350
|
+
const enumManifest = enumManifestValue != null && typeof enumManifestValue === "object" && !Array.isArray(enumManifestValue) ? enumManifestValue : void 0;
|
|
1351
|
+
return { apiManifest, modelManifest, enumManifest };
|
|
1346
1352
|
}
|
|
1347
1353
|
function loadTsconfigPathAliases() {
|
|
1348
1354
|
const tsconfigPath = resolve(WORKSPACE_ROOT, "tsconfig.base.json");
|