@dereekb/dbx-cli 13.11.3 → 13.11.5
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/firebase-api-manifest/main.js +666 -16
- package/firebase-api-manifest/package.json +1 -1
- package/index.cjs.js +593 -50
- package/index.esm.js +587 -52
- package/manifest-extract/index.cjs.js +705 -0
- package/manifest-extract/index.esm.js +705 -1
- package/manifest-extract/package.json +1 -1
- package/manifest-extract/src/index.d.ts +1 -0
- package/manifest-extract/src/lib/extract-models.d.ts +33 -0
- package/manifest-extract/src/lib/types.d.ts +148 -0
- package/package.json +5 -4
- package/src/lib/api/expand-keys.d.ts +31 -0
- package/src/lib/api/index.d.ts +1 -0
- package/src/lib/auth/oidc.flow.d.ts +7 -0
- package/src/lib/manifest/build-manifest-commands.d.ts +12 -1
- package/src/lib/manifest/build-model-info-command.d.ts +37 -0
- package/src/lib/manifest/index.d.ts +2 -0
- package/src/lib/manifest/model-info-utils.d.ts +40 -0
- package/src/lib/manifest/types.d.ts +126 -0
- package/src/lib/runner/run.d.ts +29 -0
|
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from 'node:module';
|
|
|
3
3
|
const require = __createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/main.ts
|
|
6
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as
|
|
6
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "node:fs";
|
|
7
7
|
import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve3 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/parse-functions.ts
|
|
@@ -384,6 +384,334 @@ function readJsDocSummary(node) {
|
|
|
384
384
|
return description.length > 0 ? description : void 0;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
// packages/dbx-cli/manifest-extract/src/lib/extract-models.ts
|
|
388
|
+
import { Node as Node3, Project as Project3 } from "ts-morph";
|
|
389
|
+
var IDENTITY_FN = "firestoreModelIdentity";
|
|
390
|
+
var CONVERTER_FN_NAMES = ["snapshotConverterFunctions", "firestoreSubObject", "firestoreObjectArray"];
|
|
391
|
+
var SUB_OBJECT_FN = "firestoreSubObject";
|
|
392
|
+
var OBJECT_ARRAY_FN = "firestoreObjectArray";
|
|
393
|
+
var SNAPSHOT_FN = "snapshotConverterFunctions";
|
|
394
|
+
var FIELDS_LITERAL_KEY = "fields";
|
|
395
|
+
var OBJECT_FIELD_KEY = "objectField";
|
|
396
|
+
function extractModelsFromSource(input) {
|
|
397
|
+
const project = new Project3({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
|
|
398
|
+
const sourceFile = project.createSourceFile(input.name, input.text, { overwrite: true });
|
|
399
|
+
const identities = readIdentities(sourceFile);
|
|
400
|
+
const interfaces = readInterfaces(sourceFile);
|
|
401
|
+
const converters = readConverters(sourceFile);
|
|
402
|
+
const enums = readEnums(sourceFile);
|
|
403
|
+
const modelGroups = readModelGroups(sourceFile);
|
|
404
|
+
return { identities, interfaces, converters, enums, modelGroups };
|
|
405
|
+
}
|
|
406
|
+
function readIdentities(sourceFile) {
|
|
407
|
+
const out = [];
|
|
408
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
409
|
+
if (!statement.isExported()) continue;
|
|
410
|
+
for (const decl of statement.getDeclarations()) {
|
|
411
|
+
const initializer = decl.getInitializer();
|
|
412
|
+
if (!initializer || !Node3.isCallExpression(initializer)) continue;
|
|
413
|
+
if (initializer.getExpression().getText() !== IDENTITY_FN) continue;
|
|
414
|
+
const parsed = parseIdentityArgs(initializer);
|
|
415
|
+
if (parsed) {
|
|
416
|
+
out.push({ identityConst: decl.getName(), ...parsed });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return out;
|
|
421
|
+
}
|
|
422
|
+
function parseIdentityArgs(call) {
|
|
423
|
+
const args = call.getArguments();
|
|
424
|
+
let result;
|
|
425
|
+
if (args.length === 1) {
|
|
426
|
+
const modelType = stringLiteralValue(args[0]);
|
|
427
|
+
if (modelType !== void 0) {
|
|
428
|
+
result = { modelType, collectionPrefix: void 0, parentIdentityConst: void 0 };
|
|
429
|
+
}
|
|
430
|
+
} else if (args.length === 2) {
|
|
431
|
+
const first = stringLiteralValue(args[0]);
|
|
432
|
+
if (first === void 0) {
|
|
433
|
+
const modelType = stringLiteralValue(args[1]);
|
|
434
|
+
if (modelType !== void 0) {
|
|
435
|
+
result = { modelType, collectionPrefix: void 0, parentIdentityConst: identifierName(args[0]) };
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
result = { modelType: first, collectionPrefix: stringLiteralValue(args[1]), parentIdentityConst: void 0 };
|
|
439
|
+
}
|
|
440
|
+
} else if (args.length >= 3) {
|
|
441
|
+
const modelType = stringLiteralValue(args[1]);
|
|
442
|
+
if (modelType !== void 0) {
|
|
443
|
+
result = { modelType, collectionPrefix: stringLiteralValue(args[2]), parentIdentityConst: identifierName(args[0]) };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
function readInterfaces(sourceFile) {
|
|
449
|
+
const out = [];
|
|
450
|
+
for (const decl of sourceFile.getInterfaces()) {
|
|
451
|
+
if (!decl.isExported()) continue;
|
|
452
|
+
out.push(buildInterface(decl));
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
function buildInterface(decl) {
|
|
457
|
+
const jsDocs = decl.getJsDocs();
|
|
458
|
+
const hasDbxModelTag = jsDocsHaveTag(jsDocs, "dbxModel");
|
|
459
|
+
const extendsNames = decl.getExtends().map((e) => e.getExpression().getText());
|
|
460
|
+
const props = [];
|
|
461
|
+
for (const prop of decl.getProperties()) {
|
|
462
|
+
const propJsDocs = prop.getJsDocs();
|
|
463
|
+
const longName = readJsDocTagText(propJsDocs, "dbxModelVariable");
|
|
464
|
+
const syncFlag = readJsDocTagText(propJsDocs, "dbxModelVariableSyncFlag");
|
|
465
|
+
const tsType = (prop.getTypeNode()?.getText() ?? "").replaceAll(/\s+/g, " ").trim();
|
|
466
|
+
const optional = prop.hasQuestionToken() || tsType.startsWith("Maybe<");
|
|
467
|
+
props.push({
|
|
468
|
+
name: prop.getName(),
|
|
469
|
+
tsType,
|
|
470
|
+
optional,
|
|
471
|
+
description: readJsDocDescription(propJsDocs),
|
|
472
|
+
longName,
|
|
473
|
+
syncFlag
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
name: decl.getName(),
|
|
478
|
+
description: readJsDocDescription(jsDocs),
|
|
479
|
+
hasDbxModelTag,
|
|
480
|
+
extendsNames,
|
|
481
|
+
props
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function readConverters(sourceFile) {
|
|
485
|
+
const out = [];
|
|
486
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
487
|
+
if (!statement.isExported()) continue;
|
|
488
|
+
for (const decl of statement.getDeclarations()) {
|
|
489
|
+
const initializer = decl.getInitializer();
|
|
490
|
+
if (!initializer || !Node3.isCallExpression(initializer)) continue;
|
|
491
|
+
const factory = initializer.getExpression().getText();
|
|
492
|
+
if (!isConverterFactoryName(factory)) continue;
|
|
493
|
+
const interfaceName = readGenericInterfaceName(initializer);
|
|
494
|
+
const fields = readConverterFields(initializer);
|
|
495
|
+
if (!fields) continue;
|
|
496
|
+
out.push({
|
|
497
|
+
converterConst: decl.getName(),
|
|
498
|
+
factory,
|
|
499
|
+
interfaceName,
|
|
500
|
+
fields,
|
|
501
|
+
line: decl.getStartLineNumber()
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return out;
|
|
506
|
+
}
|
|
507
|
+
function isConverterFactoryName(name) {
|
|
508
|
+
return CONVERTER_FN_NAMES.includes(name);
|
|
509
|
+
}
|
|
510
|
+
function readGenericInterfaceName(call) {
|
|
511
|
+
const typeArgs = call.getTypeArguments();
|
|
512
|
+
let result;
|
|
513
|
+
if (typeArgs.length > 0) {
|
|
514
|
+
result = typeArgs[0].getText().replaceAll(/<[^>]*>/g, "").trim();
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
function readConverterFields(call) {
|
|
519
|
+
const fnName = call.getExpression().getText();
|
|
520
|
+
const args = call.getArguments();
|
|
521
|
+
if (args.length === 0) return void 0;
|
|
522
|
+
const config = args[0];
|
|
523
|
+
if (!Node3.isObjectLiteralExpression(config)) return void 0;
|
|
524
|
+
let fieldsLiteral;
|
|
525
|
+
if (fnName === SNAPSHOT_FN) {
|
|
526
|
+
fieldsLiteral = readObjectProperty(config, FIELDS_LITERAL_KEY);
|
|
527
|
+
} else {
|
|
528
|
+
const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
|
|
529
|
+
if (objectField && Node3.isObjectLiteralExpression(objectField)) {
|
|
530
|
+
fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!fieldsLiteral) return void 0;
|
|
534
|
+
return readFieldEntries(fieldsLiteral);
|
|
535
|
+
}
|
|
536
|
+
function readFieldEntries(fields) {
|
|
537
|
+
const out = [];
|
|
538
|
+
for (const property of fields.getProperties()) {
|
|
539
|
+
if (Node3.isPropertyAssignment(property)) {
|
|
540
|
+
const initializer = property.getInitializer();
|
|
541
|
+
const converterText = initializer ? initializer.getText().replaceAll(/\s+/g, " ").trim() : "";
|
|
542
|
+
const nested = initializer ? readNestedFromExpression(initializer) : void 0;
|
|
543
|
+
out.push({
|
|
544
|
+
key: property.getName(),
|
|
545
|
+
converter: converterText,
|
|
546
|
+
nestedConverterRef: nested?.ref,
|
|
547
|
+
nestedConverterInline: nested?.inline,
|
|
548
|
+
nestedIsArray: nested?.isArray
|
|
549
|
+
});
|
|
550
|
+
} else if (Node3.isShorthandPropertyAssignment(property)) {
|
|
551
|
+
const name = property.getName();
|
|
552
|
+
out.push({ key: name, converter: name });
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return out;
|
|
556
|
+
}
|
|
557
|
+
function readNestedFromExpression(expr) {
|
|
558
|
+
if (!Node3.isCallExpression(expr)) return void 0;
|
|
559
|
+
const fnName = expr.getExpression().getText();
|
|
560
|
+
if (fnName !== SUB_OBJECT_FN && fnName !== OBJECT_ARRAY_FN) return void 0;
|
|
561
|
+
const args = expr.getArguments();
|
|
562
|
+
if (args.length === 0) return void 0;
|
|
563
|
+
const config = args[0];
|
|
564
|
+
if (!Node3.isObjectLiteralExpression(config)) return void 0;
|
|
565
|
+
const objectField = readPropertyValue(config, OBJECT_FIELD_KEY);
|
|
566
|
+
if (!objectField) return void 0;
|
|
567
|
+
const isArray = fnName === OBJECT_ARRAY_FN;
|
|
568
|
+
let result;
|
|
569
|
+
if (Node3.isIdentifier(objectField)) {
|
|
570
|
+
result = { ref: objectField.getText(), isArray };
|
|
571
|
+
} else if (Node3.isObjectLiteralExpression(objectField)) {
|
|
572
|
+
const fieldsLiteral = readObjectProperty(objectField, FIELDS_LITERAL_KEY);
|
|
573
|
+
if (fieldsLiteral) {
|
|
574
|
+
const inlineFields = readFieldEntries(fieldsLiteral);
|
|
575
|
+
result = {
|
|
576
|
+
inline: {
|
|
577
|
+
converterConst: void 0,
|
|
578
|
+
factory: fnName,
|
|
579
|
+
interfaceName: readGenericInterfaceName(expr),
|
|
580
|
+
fields: inlineFields,
|
|
581
|
+
line: expr.getStartLineNumber()
|
|
582
|
+
},
|
|
583
|
+
isArray
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
function readPropertyValue(literal, key) {
|
|
590
|
+
const property = literal.getProperty(key);
|
|
591
|
+
let result;
|
|
592
|
+
if (property && Node3.isPropertyAssignment(property)) {
|
|
593
|
+
result = property.getInitializer();
|
|
594
|
+
} else if (property && Node3.isShorthandPropertyAssignment(property)) {
|
|
595
|
+
result = property.getNameNode();
|
|
596
|
+
}
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
function readObjectProperty(literal, key) {
|
|
600
|
+
const value = readPropertyValue(literal, key);
|
|
601
|
+
return value && Node3.isObjectLiteralExpression(value) ? value : void 0;
|
|
602
|
+
}
|
|
603
|
+
function readEnums(sourceFile) {
|
|
604
|
+
const out = [];
|
|
605
|
+
for (const decl of sourceFile.getEnums()) {
|
|
606
|
+
if (!decl.isExported()) continue;
|
|
607
|
+
const values = [];
|
|
608
|
+
for (const member of decl.getMembers()) {
|
|
609
|
+
const value = member.getValue();
|
|
610
|
+
const description = readJsDocDescription(member.getJsDocs());
|
|
611
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
612
|
+
values.push({ name: member.getName(), value, description });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
out.push({
|
|
616
|
+
name: decl.getName(),
|
|
617
|
+
values,
|
|
618
|
+
description: readJsDocDescription(decl.getJsDocs())
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
function readModelGroups(sourceFile) {
|
|
624
|
+
const out = [];
|
|
625
|
+
for (const iface of sourceFile.getInterfaces()) {
|
|
626
|
+
if (!iface.isExported()) continue;
|
|
627
|
+
const groupTag = readJsDocTagText(iface.getJsDocs(), "dbxModelGroup");
|
|
628
|
+
if (!groupTag) continue;
|
|
629
|
+
const containerName = iface.getName();
|
|
630
|
+
if (!containerName.endsWith("FirestoreCollections")) continue;
|
|
631
|
+
const modelNames = [];
|
|
632
|
+
for (const prop of iface.getProperties()) {
|
|
633
|
+
const tsType = prop.getTypeNode()?.getText() ?? "";
|
|
634
|
+
const match = /([A-Z]\w*)FirestoreCollection(?:Factory)?(?:\b|<)/.exec(tsType);
|
|
635
|
+
if (match) modelNames.push(match[1]);
|
|
636
|
+
}
|
|
637
|
+
out.push({
|
|
638
|
+
name: groupTag,
|
|
639
|
+
containerName,
|
|
640
|
+
description: readJsDocDescription(iface.getJsDocs()),
|
|
641
|
+
modelNames
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
function jsDocsHaveTag(jsDocs, tagName) {
|
|
647
|
+
let found = false;
|
|
648
|
+
for (const jsDoc of jsDocs) {
|
|
649
|
+
for (const tag of jsDoc.getTags()) {
|
|
650
|
+
if (tag.getTagName() === tagName) {
|
|
651
|
+
found = true;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (found) break;
|
|
656
|
+
}
|
|
657
|
+
return found;
|
|
658
|
+
}
|
|
659
|
+
function readJsDocTagText(jsDocs, tagName) {
|
|
660
|
+
let result;
|
|
661
|
+
for (const jsDoc of jsDocs) {
|
|
662
|
+
for (const tag of jsDoc.getTags()) {
|
|
663
|
+
if (tag.getTagName() !== tagName) continue;
|
|
664
|
+
const text = tag.getCommentText()?.trim();
|
|
665
|
+
if (text !== void 0 && text.length > 0) {
|
|
666
|
+
result = text;
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (result !== void 0) break;
|
|
671
|
+
}
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
function readJsDocDescription(jsDocs) {
|
|
675
|
+
let result;
|
|
676
|
+
for (const jsDoc of jsDocs) {
|
|
677
|
+
const description = jsDoc.getDescription().trim();
|
|
678
|
+
if (description.length === 0) continue;
|
|
679
|
+
const paragraph = firstParagraph(description);
|
|
680
|
+
if (paragraph.length > 0) {
|
|
681
|
+
result = paragraph;
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return result;
|
|
686
|
+
}
|
|
687
|
+
function firstParagraph(text) {
|
|
688
|
+
const lines = text.split("\n").map((line) => line.trim());
|
|
689
|
+
const collected = [];
|
|
690
|
+
for (const line of lines) {
|
|
691
|
+
if (line.startsWith("@")) break;
|
|
692
|
+
if (line.length === 0) {
|
|
693
|
+
if (collected.length > 0) break;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
collected.push(line);
|
|
697
|
+
}
|
|
698
|
+
return collected.join(" ").trim();
|
|
699
|
+
}
|
|
700
|
+
function stringLiteralValue(node) {
|
|
701
|
+
let result;
|
|
702
|
+
if (Node3.isStringLiteral(node) || Node3.isNoSubstitutionTemplateLiteral(node)) {
|
|
703
|
+
result = node.getLiteralText();
|
|
704
|
+
}
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
function identifierName(node) {
|
|
708
|
+
let result;
|
|
709
|
+
if (Node3.isIdentifier(node)) {
|
|
710
|
+
result = node.getText();
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
|
|
387
715
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-api-files.ts
|
|
388
716
|
function findApiFiles(packageRoot) {
|
|
389
717
|
const libRoot = join2(packageRoot, "src", "lib");
|
|
@@ -424,9 +752,263 @@ function safeIsDirectory(p) {
|
|
|
424
752
|
}
|
|
425
753
|
}
|
|
426
754
|
|
|
755
|
+
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/find-model-files.ts
|
|
756
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
757
|
+
import { join as join3 } from "node:path";
|
|
758
|
+
function findModelFiles(packageRoot) {
|
|
759
|
+
const libRoot = join3(packageRoot, "src", "lib");
|
|
760
|
+
if (!safeIsDirectory2(libRoot)) return [];
|
|
761
|
+
const out = [];
|
|
762
|
+
for (const filePath of walkSourceFiles(libRoot)) {
|
|
763
|
+
const text = readFileSync4(filePath, "utf8");
|
|
764
|
+
if (!textHasModelMarker(text)) continue;
|
|
765
|
+
const extraction = extractModelsFromSource({ name: filePath, text });
|
|
766
|
+
if (extraction.identities.length === 0 && extraction.modelGroups.length === 0 && extraction.converters.length === 0) continue;
|
|
767
|
+
out.push({ filePath, extraction });
|
|
768
|
+
}
|
|
769
|
+
return out;
|
|
770
|
+
}
|
|
771
|
+
function textHasModelMarker(text) {
|
|
772
|
+
if (text.includes("firestoreModelIdentity(")) return true;
|
|
773
|
+
if (text.includes("@dbxModelGroup")) return true;
|
|
774
|
+
if (text.includes("snapshotConverterFunctions")) return true;
|
|
775
|
+
if (text.includes("firestoreSubObject")) return true;
|
|
776
|
+
if (text.includes("firestoreObjectArray")) return true;
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
function* walkSourceFiles(dir) {
|
|
780
|
+
for (const entry of readdirSync2(dir).sort()) {
|
|
781
|
+
if (entry === "node_modules" || entry === "dist") continue;
|
|
782
|
+
const p = join3(dir, entry);
|
|
783
|
+
const stat = statSync2(p);
|
|
784
|
+
if (stat.isDirectory()) {
|
|
785
|
+
yield* walkSourceFiles(p);
|
|
786
|
+
} else if (isCandidateSourceFile(entry)) {
|
|
787
|
+
yield p;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function isCandidateSourceFile(name) {
|
|
792
|
+
if (!name.endsWith(".ts")) return false;
|
|
793
|
+
if (name.endsWith(".api.ts")) return false;
|
|
794
|
+
if (name.endsWith(".spec.ts")) return false;
|
|
795
|
+
if (name.endsWith(".test.ts")) return false;
|
|
796
|
+
if (name.endsWith(".id.ts")) return false;
|
|
797
|
+
if (name.endsWith(".d.ts")) return false;
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
function safeIsDirectory2(p) {
|
|
801
|
+
try {
|
|
802
|
+
return statSync2(p).isDirectory();
|
|
803
|
+
} catch {
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/assemble-models.ts
|
|
809
|
+
var MAX_NESTED_DEPTH = 8;
|
|
810
|
+
var LONG_NAME_RE = /^[a-z][a-zA-Z0-9]*$/;
|
|
811
|
+
var ENUM_GENERIC_RE = /firestoreEnum<(\w+)>|optionalFirestoreEnum<(\w+)>/;
|
|
812
|
+
function assembleModels(input) {
|
|
813
|
+
const registries = buildGlobalRegistries(input.extractions);
|
|
814
|
+
const accumulator = { seen: /* @__PURE__ */ new Set(), entries: [] };
|
|
815
|
+
for (const source of input.extractions) {
|
|
816
|
+
appendEntriesFromSource(source, registries, accumulator);
|
|
817
|
+
}
|
|
818
|
+
accumulator.entries.sort((a, b) => a.modelType.localeCompare(b.modelType));
|
|
819
|
+
return accumulator.entries;
|
|
820
|
+
}
|
|
821
|
+
function buildGlobalRegistries(extractions) {
|
|
822
|
+
const converterRegistry = /* @__PURE__ */ new Map();
|
|
823
|
+
const interfaceRegistry = /* @__PURE__ */ new Map();
|
|
824
|
+
const groupByModelName = /* @__PURE__ */ new Map();
|
|
825
|
+
for (const { extraction } of extractions) {
|
|
826
|
+
for (const converter of extraction.converters) {
|
|
827
|
+
if (converter.converterConst && !converterRegistry.has(converter.converterConst)) {
|
|
828
|
+
converterRegistry.set(converter.converterConst, converter);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
for (const iface of extraction.interfaces) {
|
|
832
|
+
if (!interfaceRegistry.has(iface.name)) interfaceRegistry.set(iface.name, iface);
|
|
833
|
+
}
|
|
834
|
+
for (const group of extraction.modelGroups) {
|
|
835
|
+
for (const modelName of group.modelNames) {
|
|
836
|
+
if (!groupByModelName.has(modelName)) groupByModelName.set(modelName, group.name);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return { converterRegistry, interfaceRegistry, groupByModelName };
|
|
841
|
+
}
|
|
842
|
+
function appendEntriesFromSource(source, registries, accumulator) {
|
|
843
|
+
const enumNames = new Set(source.extraction.enums.map((e) => e.name));
|
|
844
|
+
for (const identity of source.extraction.identities) {
|
|
845
|
+
if (accumulator.seen.has(identity.identityConst)) continue;
|
|
846
|
+
const entry = buildEntryForIdentity({ identity, source, registries, enumNames });
|
|
847
|
+
if (entry) {
|
|
848
|
+
accumulator.seen.add(identity.identityConst);
|
|
849
|
+
accumulator.entries.push(entry);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function buildEntryForIdentity(input) {
|
|
854
|
+
const { identity, source, registries, enumNames } = input;
|
|
855
|
+
if (identity.collectionPrefix === void 0) return void 0;
|
|
856
|
+
const modelName = capitalize(identity.modelType);
|
|
857
|
+
const iface = registries.interfaceRegistry.get(modelName);
|
|
858
|
+
if (!iface?.hasDbxModelTag) return void 0;
|
|
859
|
+
const converter = findConverterForInterface(source.extraction, modelName) ?? findConverterFromRegistry(registries.converterRegistry, modelName);
|
|
860
|
+
if (!converter) return void 0;
|
|
861
|
+
const fields = buildFields({
|
|
862
|
+
converter,
|
|
863
|
+
iface,
|
|
864
|
+
interfaceRegistry: registries.interfaceRegistry,
|
|
865
|
+
converterRegistry: registries.converterRegistry,
|
|
866
|
+
enumNames,
|
|
867
|
+
depth: 0,
|
|
868
|
+
visitedConverters: /* @__PURE__ */ new Set()
|
|
869
|
+
});
|
|
870
|
+
const modelGroup = registries.groupByModelName.get(modelName);
|
|
871
|
+
return {
|
|
872
|
+
modelType: identity.modelType,
|
|
873
|
+
modelName,
|
|
874
|
+
...modelGroup ? { modelGroup } : {},
|
|
875
|
+
identityConst: identity.identityConst,
|
|
876
|
+
collectionPrefix: identity.collectionPrefix,
|
|
877
|
+
...identity.parentIdentityConst ? { parentIdentityConst: identity.parentIdentityConst } : {},
|
|
878
|
+
...iface.description ? { description: iface.description } : {},
|
|
879
|
+
sourcePackage: source.sourcePackage,
|
|
880
|
+
sourceFile: source.sourceFile,
|
|
881
|
+
fields
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function findConverterForInterface(extraction, interfaceName) {
|
|
885
|
+
return extraction.converters.find((c) => c.interfaceName === interfaceName);
|
|
886
|
+
}
|
|
887
|
+
function findConverterFromRegistry(registry, interfaceName) {
|
|
888
|
+
let result;
|
|
889
|
+
for (const converter of registry.values()) {
|
|
890
|
+
if (converter.interfaceName === interfaceName) {
|
|
891
|
+
result = converter;
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return result;
|
|
896
|
+
}
|
|
897
|
+
function buildFields(input) {
|
|
898
|
+
const out = [];
|
|
899
|
+
const propByName = /* @__PURE__ */ new Map();
|
|
900
|
+
if (input.iface) {
|
|
901
|
+
for (const prop of input.iface.props) propByName.set(prop.name, prop);
|
|
902
|
+
for (const ancestor of collectAncestors(input.iface, input.interfaceRegistry)) {
|
|
903
|
+
for (const prop of ancestor.props) {
|
|
904
|
+
if (!propByName.has(prop.name)) propByName.set(prop.name, prop);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
for (const field of input.converter.fields) {
|
|
909
|
+
out.push(buildField({ ...input, field, propByName }));
|
|
910
|
+
}
|
|
911
|
+
return out;
|
|
912
|
+
}
|
|
913
|
+
function buildField(input) {
|
|
914
|
+
const { field, propByName } = input;
|
|
915
|
+
const prop = propByName.get(field.key);
|
|
916
|
+
const enumRef = resolveEnumRef(field.converter, prop?.tsType, input.enumNames);
|
|
917
|
+
const optional = prop?.optional ?? field.converter.startsWith("optionalFirestore");
|
|
918
|
+
const longName = resolveLongName(field.key, prop?.longName);
|
|
919
|
+
const nested = resolveNestedFields(input);
|
|
920
|
+
const out = {
|
|
921
|
+
name: field.key,
|
|
922
|
+
longName,
|
|
923
|
+
converter: field.converter,
|
|
924
|
+
...prop?.tsType ? { tsType: prop.tsType } : {},
|
|
925
|
+
optional,
|
|
926
|
+
...prop?.description ? { description: prop.description } : {},
|
|
927
|
+
...enumRef ? { enumRef } : {},
|
|
928
|
+
...prop?.syncFlag ? { syncFlag: prop.syncFlag } : {},
|
|
929
|
+
...nested ? { nestedFields: nested.fields, nestedIsArray: nested.isArray } : {}
|
|
930
|
+
};
|
|
931
|
+
return out;
|
|
932
|
+
}
|
|
933
|
+
function resolveNestedFields(input) {
|
|
934
|
+
const { field } = input;
|
|
935
|
+
if (input.depth >= MAX_NESTED_DEPTH) return void 0;
|
|
936
|
+
let nestedConverter;
|
|
937
|
+
if (field.nestedConverterInline) {
|
|
938
|
+
nestedConverter = field.nestedConverterInline;
|
|
939
|
+
} else if (field.nestedConverterRef) {
|
|
940
|
+
if (input.visitedConverters.has(field.nestedConverterRef)) return void 0;
|
|
941
|
+
nestedConverter = input.converterRegistry.get(field.nestedConverterRef);
|
|
942
|
+
}
|
|
943
|
+
if (!nestedConverter) return void 0;
|
|
944
|
+
const nextVisited = new Set(input.visitedConverters);
|
|
945
|
+
if (nestedConverter.converterConst) nextVisited.add(nestedConverter.converterConst);
|
|
946
|
+
const nestedIface = nestedConverter.interfaceName ? input.interfaceRegistry.get(nestedConverter.interfaceName) : void 0;
|
|
947
|
+
const fields = buildFields({
|
|
948
|
+
converter: nestedConverter,
|
|
949
|
+
iface: nestedIface,
|
|
950
|
+
interfaceRegistry: input.interfaceRegistry,
|
|
951
|
+
converterRegistry: input.converterRegistry,
|
|
952
|
+
enumNames: input.enumNames,
|
|
953
|
+
depth: input.depth + 1,
|
|
954
|
+
visitedConverters: nextVisited
|
|
955
|
+
});
|
|
956
|
+
return { fields, isArray: field.nestedIsArray ?? false };
|
|
957
|
+
}
|
|
958
|
+
function collectAncestors(iface, registry) {
|
|
959
|
+
const out = [];
|
|
960
|
+
const visited = /* @__PURE__ */ new Set([iface.name]);
|
|
961
|
+
const stack = [iface];
|
|
962
|
+
while (stack.length > 0) {
|
|
963
|
+
const current = stack.pop();
|
|
964
|
+
for (const parentName of current.extendsNames) {
|
|
965
|
+
if (visited.has(parentName)) continue;
|
|
966
|
+
visited.add(parentName);
|
|
967
|
+
const parent = registry.get(parentName);
|
|
968
|
+
if (parent) {
|
|
969
|
+
out.push(parent);
|
|
970
|
+
stack.push(parent);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return out;
|
|
975
|
+
}
|
|
976
|
+
function resolveLongName(fieldName, propLongName) {
|
|
977
|
+
let result;
|
|
978
|
+
if (propLongName && LONG_NAME_RE.test(propLongName)) {
|
|
979
|
+
result = propLongName;
|
|
980
|
+
} else {
|
|
981
|
+
result = fieldName;
|
|
982
|
+
}
|
|
983
|
+
return result;
|
|
984
|
+
}
|
|
985
|
+
function resolveEnumRef(converter, tsType, enumNames) {
|
|
986
|
+
let result;
|
|
987
|
+
if (tsType) {
|
|
988
|
+
for (const name of enumNames) {
|
|
989
|
+
const re = new RegExp(String.raw`\b${name}\b`);
|
|
990
|
+
if (re.test(tsType)) {
|
|
991
|
+
result = name;
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (!result) {
|
|
997
|
+
const m = ENUM_GENERIC_RE.exec(converter);
|
|
998
|
+
if (m) {
|
|
999
|
+
const name = m[1] ?? m[2];
|
|
1000
|
+
if (enumNames.has(name)) result = name;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return result;
|
|
1004
|
+
}
|
|
1005
|
+
function capitalize(s) {
|
|
1006
|
+
return s.length > 0 ? s[0].toUpperCase() + s.slice(1) : s;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
427
1009
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/bind-validators.ts
|
|
428
|
-
import { existsSync as existsSync2, readdirSync as
|
|
429
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join as
|
|
1010
|
+
import { existsSync as existsSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
|
|
1011
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
|
|
430
1012
|
function deriveValidatorName(paramsTypeName) {
|
|
431
1013
|
if (!paramsTypeName) return "";
|
|
432
1014
|
return paramsTypeName.charAt(0).toLowerCase() + paramsTypeName.slice(1) + "Type";
|
|
@@ -438,7 +1020,7 @@ function isExportedFromPackage(input) {
|
|
|
438
1020
|
return findIdentifierInBarrelChain(indexPath, identifier, /* @__PURE__ */ new Set());
|
|
439
1021
|
}
|
|
440
1022
|
function locateBarrelEntry(packageRoot) {
|
|
441
|
-
const candidates = [
|
|
1023
|
+
const candidates = [join4(packageRoot, "src", "index.ts"), join4(packageRoot, "src", "index.d.ts"), join4(packageRoot, "index.d.ts"), join4(packageRoot, "index.ts")];
|
|
442
1024
|
return candidates.find((candidate) => existsSync2(candidate));
|
|
443
1025
|
}
|
|
444
1026
|
var EXPORT_DECL_PATTERNS = [/export\s+(?:declare\s+)?const\s+IDENT\b/, /export\s+(?:declare\s+)?function\s+IDENT\b/, /export\s*\{[^}]*\bIDENT\b[^}]*\}/];
|
|
@@ -447,7 +1029,7 @@ function findIdentifierInBarrelChain(filePath, identifier, visited) {
|
|
|
447
1029
|
visited.add(filePath);
|
|
448
1030
|
let text;
|
|
449
1031
|
try {
|
|
450
|
-
text =
|
|
1032
|
+
text = readFileSync5(filePath, "utf8");
|
|
451
1033
|
} catch {
|
|
452
1034
|
return false;
|
|
453
1035
|
}
|
|
@@ -491,12 +1073,12 @@ function hasTsModuleExtension(value) {
|
|
|
491
1073
|
}
|
|
492
1074
|
function resolveExistingTsPath(probe) {
|
|
493
1075
|
if (!existsSync2(probe)) return void 0;
|
|
494
|
-
const stat =
|
|
1076
|
+
const stat = statSync3(probe);
|
|
495
1077
|
if (stat.isFile()) return probe;
|
|
496
1078
|
if (!stat.isDirectory()) return void 0;
|
|
497
|
-
const sourceIndex =
|
|
1079
|
+
const sourceIndex = join4(probe, "index.ts");
|
|
498
1080
|
if (existsSync2(sourceIndex)) return sourceIndex;
|
|
499
|
-
const declarationIndex =
|
|
1081
|
+
const declarationIndex = join4(probe, "index.d.ts");
|
|
500
1082
|
return existsSync2(declarationIndex) ? declarationIndex : void 0;
|
|
501
1083
|
}
|
|
502
1084
|
function escapeRegExp(value) {
|
|
@@ -506,7 +1088,7 @@ function escapeRegExp(value) {
|
|
|
506
1088
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/emit.ts
|
|
507
1089
|
import { format, resolveConfig } from "prettier";
|
|
508
1090
|
async function renderManifest(input) {
|
|
509
|
-
const { outputFile, entries, projectName, namespace } = input;
|
|
1091
|
+
const { outputFile, entries, projectName, namespace, modelEntries, modelNamespace } = input;
|
|
510
1092
|
const importsByPackage = /* @__PURE__ */ new Map();
|
|
511
1093
|
for (const entry of entries) {
|
|
512
1094
|
if (!entry.packageName || !entry.validatorName) continue;
|
|
@@ -519,16 +1101,24 @@ async function renderManifest(input) {
|
|
|
519
1101
|
return `import { ${sortedNames} } from '${pkg}';`;
|
|
520
1102
|
});
|
|
521
1103
|
const entryLines = entries.map((e) => renderEntry(e));
|
|
1104
|
+
const emitModels = Boolean(modelEntries && modelEntries.length > 0 && modelNamespace);
|
|
1105
|
+
const dbxCliTypeImports = emitModels ? `import { type CliApiManifest, type CliModelManifest } from '@dereekb/dbx-cli';` : `import { type CliApiManifest } from '@dereekb/dbx-cli';`;
|
|
1106
|
+
const modelSection = emitModels ? `
|
|
1107
|
+
|
|
1108
|
+
export const ${modelNamespace}: CliModelManifest = [
|
|
1109
|
+
${(modelEntries ?? []).map((m) => renderModelEntry(m)).join(",\n")}
|
|
1110
|
+
];
|
|
1111
|
+
` : "";
|
|
522
1112
|
const source = `/* eslint-disable @nx/enforce-module-boundaries */
|
|
523
1113
|
// AUTO-GENERATED \u2014 DO NOT EDIT.
|
|
524
1114
|
// Run \`pnpm nx run ${projectName}:generate-api-manifest\` to refresh.
|
|
525
1115
|
|
|
526
1116
|
${importLines.join("\n")}
|
|
527
|
-
|
|
1117
|
+
${dbxCliTypeImports}
|
|
528
1118
|
|
|
529
1119
|
export const ${namespace}: CliApiManifest = [
|
|
530
1120
|
${entryLines.join(",\n")}
|
|
531
|
-
]
|
|
1121
|
+
];${modelSection}
|
|
532
1122
|
`;
|
|
533
1123
|
return formatWithPrettier(source, outputFile);
|
|
534
1124
|
}
|
|
@@ -562,6 +1152,41 @@ async function formatWithPrettier(source, outputFile) {
|
|
|
562
1152
|
const config = await resolveConfig(outputFile);
|
|
563
1153
|
return format(source, { ...config, filepath: outputFile });
|
|
564
1154
|
}
|
|
1155
|
+
function renderModelEntry(entry) {
|
|
1156
|
+
const fields = [
|
|
1157
|
+
`modelType: ${JSON.stringify(entry.modelType)}`,
|
|
1158
|
+
`modelName: ${JSON.stringify(entry.modelName)}`,
|
|
1159
|
+
entry.modelGroup ? `modelGroup: ${JSON.stringify(entry.modelGroup)}` : void 0,
|
|
1160
|
+
`identityConst: ${JSON.stringify(entry.identityConst)}`,
|
|
1161
|
+
`collectionPrefix: ${JSON.stringify(entry.collectionPrefix)}`,
|
|
1162
|
+
entry.parentIdentityConst ? `parentIdentityConst: ${JSON.stringify(entry.parentIdentityConst)}` : void 0,
|
|
1163
|
+
entry.description ? `description: ${JSON.stringify(entry.description)}` : void 0,
|
|
1164
|
+
`sourcePackage: ${JSON.stringify(entry.sourcePackage)}`,
|
|
1165
|
+
`sourceFile: ${JSON.stringify(entry.sourceFile)}`,
|
|
1166
|
+
`fields: ${renderModelFields(entry.fields)}`
|
|
1167
|
+
];
|
|
1168
|
+
return ` { ${fields.filter((v) => Boolean(v)).join(", ")} }`;
|
|
1169
|
+
}
|
|
1170
|
+
function renderModelFields(fields) {
|
|
1171
|
+
if (fields.length === 0) return "[]";
|
|
1172
|
+
const items = fields.map((field) => renderModelField(field));
|
|
1173
|
+
return `[${items.join(", ")}]`;
|
|
1174
|
+
}
|
|
1175
|
+
function renderModelField(field) {
|
|
1176
|
+
const parts = [
|
|
1177
|
+
`name: ${JSON.stringify(field.name)}`,
|
|
1178
|
+
`longName: ${JSON.stringify(field.longName)}`,
|
|
1179
|
+
`converter: ${JSON.stringify(field.converter)}`,
|
|
1180
|
+
field.tsType ? `tsType: ${JSON.stringify(field.tsType)}` : void 0,
|
|
1181
|
+
`optional: ${field.optional ? "true" : "false"}`,
|
|
1182
|
+
field.description ? `description: ${JSON.stringify(field.description)}` : void 0,
|
|
1183
|
+
field.enumRef ? `enumRef: ${JSON.stringify(field.enumRef)}` : void 0,
|
|
1184
|
+
field.syncFlag ? `syncFlag: ${JSON.stringify(field.syncFlag)}` : void 0,
|
|
1185
|
+
field.nestedFields ? `nestedFields: ${renderModelFields(field.nestedFields)}` : void 0,
|
|
1186
|
+
field.nestedFields ? `nestedIsArray: ${field.nestedIsArray ? "true" : "false"}` : void 0
|
|
1187
|
+
];
|
|
1188
|
+
return `{ ${parts.filter((v) => Boolean(v)).join(", ")} }`;
|
|
1189
|
+
}
|
|
565
1190
|
|
|
566
1191
|
// packages/dbx-cli/firebase-api-manifest/src/generate-api-manifest/main.ts
|
|
567
1192
|
var WORKSPACE_ROOT = process.cwd();
|
|
@@ -586,6 +1211,8 @@ async function main() {
|
|
|
586
1211
|
const packageCache = /* @__PURE__ */ new Map();
|
|
587
1212
|
const apiFilesCache = /* @__PURE__ */ new Map();
|
|
588
1213
|
const collected = [];
|
|
1214
|
+
const modelSources = [];
|
|
1215
|
+
const modelPackagesScanned = /* @__PURE__ */ new Set();
|
|
589
1216
|
let missingValidators = 0;
|
|
590
1217
|
let skippedGroups = 0;
|
|
591
1218
|
for (const group of groups) {
|
|
@@ -597,6 +1224,16 @@ async function main() {
|
|
|
597
1224
|
}
|
|
598
1225
|
if (!packageCache.has(pkg.packageRoot)) packageCache.set(pkg.packageRoot, pkg);
|
|
599
1226
|
if (!apiFilesCache.has(pkg.packageRoot)) apiFilesCache.set(pkg.packageRoot, findApiFiles(pkg.packageRoot));
|
|
1227
|
+
if (flags.emitModels && !modelPackagesScanned.has(pkg.packageRoot)) {
|
|
1228
|
+
modelPackagesScanned.add(pkg.packageRoot);
|
|
1229
|
+
for (const match2 of findModelFiles(pkg.packageRoot)) {
|
|
1230
|
+
modelSources.push({
|
|
1231
|
+
sourcePackage: pkg.packageName,
|
|
1232
|
+
sourceFile: relPath(WORKSPACE_ROOT, match2.filePath),
|
|
1233
|
+
extraction: match2.extraction
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
600
1237
|
const apiFiles = apiFilesCache.get(pkg.packageRoot) ?? [];
|
|
601
1238
|
const match = apiFiles.find((f) => f.className === group.className);
|
|
602
1239
|
if (!match) {
|
|
@@ -631,16 +1268,19 @@ async function main() {
|
|
|
631
1268
|
}
|
|
632
1269
|
}
|
|
633
1270
|
collected.sort(compareEntries);
|
|
1271
|
+
const modelEntries = flags.emitModels ? assembleModels({ extractions: modelSources }) : [];
|
|
1272
|
+
const filteredModelEntries = flags.only ? modelEntries.filter((m) => flags.only?.has(m.modelType)) : modelEntries;
|
|
634
1273
|
ensureOutputDir(outputDir);
|
|
635
|
-
const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace });
|
|
636
|
-
if (existsSync3(outputFile) &&
|
|
1274
|
+
const formatted = await renderManifest({ outputFile, entries: collected, projectName, namespace, modelEntries: filteredModelEntries, modelNamespace: deriveModelNamespace(flags.project) });
|
|
1275
|
+
if (existsSync3(outputFile) && readFileSync6(outputFile, "utf8") === formatted) {
|
|
637
1276
|
console.log(`[unchanged] ${relative2(WORKSPACE_ROOT, outputFile)}`);
|
|
638
1277
|
} else {
|
|
639
1278
|
writeFileSync(outputFile, formatted);
|
|
640
1279
|
console.log(`[wrote] ${relative2(WORKSPACE_ROOT, outputFile)}`);
|
|
641
1280
|
}
|
|
642
1281
|
const groupCount = packageCache.size === 0 ? 0 : new Set(collected.map((c) => c.entry.groupName)).size;
|
|
643
|
-
|
|
1282
|
+
const modelSummary = flags.emitModels ? ` \xB7 ${filteredModelEntries.length} models` : "";
|
|
1283
|
+
console.log(`Summary: ${groupCount} groups \xB7 ${collected.length} entries \xB7 ${collected.length - missingValidators} validators bound \xB7 ${missingValidators} missing \xB7 ${skippedGroups} skipped${modelSummary}`);
|
|
644
1284
|
if (flags.strict && missingValidators > 0) {
|
|
645
1285
|
console.error(`[strict] ${missingValidators} validator(s) missing \u2014 failing build.`);
|
|
646
1286
|
process.exit(1);
|
|
@@ -661,15 +1301,22 @@ function deriveNamespace(projectName) {
|
|
|
661
1301
|
const base = (projectName ?? "cli").replaceAll(/[^a-zA-Z0-9]+/g, "_");
|
|
662
1302
|
return `${base.toUpperCase()}_API_MANIFEST`;
|
|
663
1303
|
}
|
|
1304
|
+
function deriveModelNamespace(projectName) {
|
|
1305
|
+
const base = (projectName ?? "cli").replaceAll(/[^a-zA-Z0-9]+/g, "_");
|
|
1306
|
+
return `${base.toUpperCase()}_MODEL_MANIFEST`;
|
|
1307
|
+
}
|
|
664
1308
|
function parseFlags(argv) {
|
|
665
1309
|
let only;
|
|
666
1310
|
let strict = false;
|
|
667
1311
|
let functionsConfig;
|
|
668
1312
|
let output;
|
|
669
1313
|
let project;
|
|
1314
|
+
let emitModels = false;
|
|
670
1315
|
for (const arg of argv) {
|
|
671
1316
|
if (arg === "--strict") {
|
|
672
1317
|
strict = true;
|
|
1318
|
+
} else if (arg === "--emit-models") {
|
|
1319
|
+
emitModels = true;
|
|
673
1320
|
} else if (arg.startsWith("--only=")) {
|
|
674
1321
|
const list = arg.slice("--only=".length).split(",").map((s) => s.trim()).filter(Boolean);
|
|
675
1322
|
if (list.length > 0) only = new Set(list);
|
|
@@ -681,7 +1328,7 @@ function parseFlags(argv) {
|
|
|
681
1328
|
project = arg.slice("--project=".length);
|
|
682
1329
|
}
|
|
683
1330
|
}
|
|
684
|
-
return { only, strict, functionsConfig, output, project };
|
|
1331
|
+
return { only, strict, functionsConfig, output, project, emitModels };
|
|
685
1332
|
}
|
|
686
1333
|
function printUsageAndExit() {
|
|
687
1334
|
console.error(String.raw`generate-api-manifest
|
|
@@ -700,7 +1347,10 @@ Required flags:
|
|
|
700
1347
|
Optional:
|
|
701
1348
|
--project=<name> Project name to show in the regenerate banner.
|
|
702
1349
|
--only=<csv> Filter to listed model names.
|
|
703
|
-
--strict Fail when any validator binding is missing
|
|
1350
|
+
--strict Fail when any validator binding is missing.
|
|
1351
|
+
--emit-models Opt in to emitting <NAMESPACE>_MODEL_MANIFEST. Off by default —
|
|
1352
|
+
pair with the runtime \`modelManifest\` option on \`runCli\` to
|
|
1353
|
+
enable the built-in \`model-info\` command.`);
|
|
704
1354
|
process.exit(1);
|
|
705
1355
|
}
|
|
706
1356
|
try {
|