js-bao 0.2.8

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.
@@ -0,0 +1,1325 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/codegen.ts
27
+ var import_commander = require("commander");
28
+ var path3 = __toESM(require("path"), 1);
29
+
30
+ // src/cli/utils/FileUtils.ts
31
+ var fs = __toESM(require("fs"), 1);
32
+ var path = __toESM(require("path"), 1);
33
+ var import_util = require("util");
34
+ var readFile2 = (0, import_util.promisify)(fs.readFile);
35
+ var writeFile2 = (0, import_util.promisify)(fs.writeFile);
36
+ var readdir2 = (0, import_util.promisify)(fs.readdir);
37
+ var stat2 = (0, import_util.promisify)(fs.stat);
38
+ var mkdir2 = (0, import_util.promisify)(fs.mkdir);
39
+ var FileUtils = class {
40
+ /**
41
+ * Recursively find all TypeScript files in a directory
42
+ */
43
+ static async findTSFiles(dir, exclude = []) {
44
+ const files = [];
45
+ const isExcluded = (filePath) => {
46
+ return exclude.some((pattern) => {
47
+ if (pattern.includes("*")) {
48
+ const regex = new RegExp(pattern.replace(/\*/g, ".*"));
49
+ return regex.test(filePath);
50
+ }
51
+ return filePath.includes(pattern);
52
+ });
53
+ };
54
+ const scanDirectory = async (currentDir) => {
55
+ try {
56
+ const entries = await readdir2(currentDir);
57
+ for (const entry of entries) {
58
+ const fullPath = path.join(currentDir, entry);
59
+ const stats = await stat2(fullPath);
60
+ if (stats.isDirectory()) {
61
+ if (!isExcluded(fullPath)) {
62
+ await scanDirectory(fullPath);
63
+ }
64
+ } else if (stats.isFile() && entry.endsWith(".ts") && !entry.endsWith(".d.ts")) {
65
+ if (!isExcluded(fullPath)) {
66
+ files.push(fullPath);
67
+ }
68
+ }
69
+ }
70
+ } catch (error) {
71
+ console.warn(
72
+ `[FileUtils] Could not scan directory ${currentDir}:`,
73
+ error
74
+ );
75
+ }
76
+ };
77
+ await scanDirectory(dir);
78
+ return files;
79
+ }
80
+ /**
81
+ * Read and return the content of a file
82
+ */
83
+ static async readFileContent(filePath) {
84
+ try {
85
+ return await readFile2(filePath, "utf8");
86
+ } catch (error) {
87
+ throw new Error(`Failed to read file ${filePath}: ${error}`);
88
+ }
89
+ }
90
+ /**
91
+ * Write content to a file, creating directories if necessary
92
+ */
93
+ static async writeFileContent(filePath, content) {
94
+ try {
95
+ const dir = path.dirname(filePath);
96
+ await this.ensureDirectoryExists(dir);
97
+ await writeFile2(filePath, content, "utf8");
98
+ } catch (error) {
99
+ throw new Error(`Failed to write file ${filePath}: ${error}`);
100
+ }
101
+ }
102
+ /**
103
+ * Ensure a directory exists, creating it recursively if necessary
104
+ */
105
+ static async ensureDirectoryExists(dirPath) {
106
+ try {
107
+ await stat2(dirPath);
108
+ } catch {
109
+ await mkdir2(dirPath, { recursive: true });
110
+ }
111
+ }
112
+ /**
113
+ * Get relative path from one file to another
114
+ */
115
+ static getRelativePath(from, to) {
116
+ let relativePath = path.relative(path.dirname(from), to);
117
+ if (relativePath.endsWith(".ts")) {
118
+ relativePath = relativePath.slice(0, -3);
119
+ }
120
+ if (!relativePath.startsWith(".")) {
121
+ relativePath = "./" + relativePath;
122
+ }
123
+ return relativePath;
124
+ }
125
+ /**
126
+ * Check if a file exists
127
+ */
128
+ static async fileExists(filePath) {
129
+ try {
130
+ await stat2(filePath);
131
+ return true;
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+ };
137
+
138
+ // src/cli/analyzers/ModelAnalyzer.ts
139
+ var ts = __toESM(require("typescript"), 1);
140
+
141
+ // src/cli/utils/Logger.ts
142
+ var Logger = class _Logger {
143
+ static isVerbose = false;
144
+ static setVerbose(verbose) {
145
+ _Logger.isVerbose = verbose;
146
+ }
147
+ static log(message, prefix) {
148
+ const fullMessage = prefix ? `[${prefix}] ${message}` : message;
149
+ console.log(fullMessage);
150
+ }
151
+ static verbose(message, prefix) {
152
+ if (_Logger.isVerbose) {
153
+ const fullMessage = prefix ? `[${prefix}] ${message}` : message;
154
+ console.log(fullMessage);
155
+ }
156
+ }
157
+ static warn(message, prefix) {
158
+ const fullMessage = prefix ? `[${prefix}] ${message}` : message;
159
+ console.warn(fullMessage);
160
+ }
161
+ static error(message, prefix) {
162
+ const fullMessage = prefix ? `[${prefix}] ${message}` : message;
163
+ console.error(fullMessage);
164
+ }
165
+ };
166
+
167
+ // src/cli/analyzers/ModelAnalyzer.ts
168
+ var ModelAnalyzer = class {
169
+ program;
170
+ verbose;
171
+ constructor(filePaths, verbose = false) {
172
+ this.verbose = verbose;
173
+ this.program = ts.createProgram(filePaths, {
174
+ target: ts.ScriptTarget.ES2020,
175
+ module: ts.ModuleKind.CommonJS,
176
+ allowJs: false,
177
+ declaration: true,
178
+ esModuleInterop: true,
179
+ skipLibCheck: true
180
+ });
181
+ }
182
+ /**
183
+ * Analyze all model files and extract metadata
184
+ */
185
+ async analyzeModels() {
186
+ const models = [];
187
+ for (const sourceFile of this.program.getSourceFiles()) {
188
+ if (sourceFile.isDeclarationFile) continue;
189
+ const modelMetadata = this.analyzeFile(sourceFile);
190
+ if (modelMetadata) {
191
+ Logger.verbose(
192
+ `Found model: ${modelMetadata.className} with ${Object.keys(modelMetadata.relationships).length} relationships`,
193
+ "ModelAnalyzer"
194
+ );
195
+ models.push(modelMetadata);
196
+ }
197
+ }
198
+ return models;
199
+ }
200
+ /**
201
+ * Analyze a single file for model definitions
202
+ */
203
+ analyzeFile(sourceFile) {
204
+ let modelMetadata = null;
205
+ const schemaDefinitions = this.extractSchemaDefinitions(sourceFile);
206
+ const attachedSchemas = this.extractAttachedSchemas(sourceFile);
207
+ const visit = (node) => {
208
+ if (modelMetadata) return;
209
+ if (ts.isClassDeclaration(node) && node.name) {
210
+ const className = node.name.text;
211
+ if (this.extendsBaseModel(node)) {
212
+ Logger.verbose(`${className} extends BaseModel`, "ModelAnalyzer");
213
+ const schemaMetadata = this.findSchemaForClass(
214
+ node,
215
+ schemaDefinitions,
216
+ attachedSchemas
217
+ );
218
+ if (schemaMetadata) {
219
+ modelMetadata = {
220
+ className,
221
+ modelName: schemaMetadata.modelName,
222
+ schemaIdentifier: schemaMetadata.identifier,
223
+ filePath: sourceFile.fileName,
224
+ relationships: schemaMetadata.relationships,
225
+ fields: {}
226
+ };
227
+ }
228
+ }
229
+ } else if (ts.isVariableStatement(node)) {
230
+ const metadataFromFactory = this.extractCreateModelClassMetadata(
231
+ node,
232
+ schemaDefinitions,
233
+ sourceFile.fileName
234
+ );
235
+ if (metadataFromFactory) {
236
+ modelMetadata = metadataFromFactory;
237
+ }
238
+ }
239
+ if (!modelMetadata) {
240
+ ts.forEachChild(node, visit);
241
+ }
242
+ };
243
+ visit(sourceFile);
244
+ return modelMetadata;
245
+ }
246
+ /**
247
+ * Check if a class extends BaseModel
248
+ */
249
+ extendsBaseModel(node) {
250
+ if (!node.heritageClauses) return false;
251
+ for (const heritage of node.heritageClauses) {
252
+ if (heritage.token === ts.SyntaxKind.ExtendsKeyword) {
253
+ for (const type of heritage.types) {
254
+ if (ts.isIdentifier(type.expression) && type.expression.text === "BaseModel") {
255
+ return true;
256
+ }
257
+ }
258
+ }
259
+ }
260
+ return false;
261
+ }
262
+ /**
263
+ * Extract defineModelSchema declarations
264
+ */
265
+ extractSchemaDefinitions(sourceFile) {
266
+ const schemas = /* @__PURE__ */ new Map();
267
+ const visit = (node) => {
268
+ if (ts.isVariableStatement(node) && node.declarationList.declarations.length > 0) {
269
+ for (const declaration of node.declarationList.declarations) {
270
+ if (declaration.name && ts.isIdentifier(declaration.name) && declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "defineModelSchema" && declaration.initializer.arguments.length > 0) {
271
+ const arg = declaration.initializer.arguments[0];
272
+ if (ts.isObjectLiteralExpression(arg)) {
273
+ const schemaInfo = this.parseSchemaObject(arg);
274
+ if (schemaInfo) {
275
+ schemas.set(declaration.name.text, {
276
+ identifier: declaration.name.text,
277
+ ...schemaInfo
278
+ });
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
284
+ ts.forEachChild(node, visit);
285
+ };
286
+ visit(sourceFile);
287
+ return schemas;
288
+ }
289
+ parseSchemaObject(obj) {
290
+ let modelName = null;
291
+ let relationships = {};
292
+ for (const property of obj.properties) {
293
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
294
+ const propName = property.name.text;
295
+ if (propName === "name" && ts.isStringLiteral(property.initializer)) {
296
+ modelName = property.initializer.text;
297
+ } else if (propName === "options" && ts.isObjectLiteralExpression(property.initializer)) {
298
+ relationships = this.parseRelationshipsFromOptions(property.initializer);
299
+ }
300
+ }
301
+ }
302
+ return modelName ? { modelName, relationships } : null;
303
+ }
304
+ parseRelationshipsFromOptions(obj) {
305
+ for (const property of obj.properties) {
306
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
307
+ if (property.name.text === "relationships" && ts.isObjectLiteralExpression(property.initializer)) {
308
+ return this.parseRelationships(property.initializer);
309
+ }
310
+ }
311
+ }
312
+ return {};
313
+ }
314
+ /**
315
+ * Parse relationships object from @Model decorator
316
+ */
317
+ parseRelationships(obj) {
318
+ const relationships = {};
319
+ Logger.verbose(
320
+ `parseRelationships - properties count: ${obj.properties.length}`,
321
+ "ModelAnalyzer"
322
+ );
323
+ for (const property of obj.properties) {
324
+ Logger.verbose(`Processing property: ${property.kind}`, "ModelAnalyzer");
325
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
326
+ const relationshipName = property.name.text;
327
+ Logger.verbose(
328
+ `Found relationship property: ${relationshipName}`,
329
+ "ModelAnalyzer"
330
+ );
331
+ let objLiteral = null;
332
+ if (ts.isObjectLiteralExpression(property.initializer)) {
333
+ Logger.verbose(
334
+ "Property initializer is object literal",
335
+ "ModelAnalyzer"
336
+ );
337
+ objLiteral = property.initializer;
338
+ } else if (ts.isAsExpression(property.initializer) && ts.isObjectLiteralExpression(property.initializer.expression)) {
339
+ Logger.verbose(
340
+ "Property initializer is object literal with type assertion",
341
+ "ModelAnalyzer"
342
+ );
343
+ objLiteral = property.initializer.expression;
344
+ } else {
345
+ Logger.verbose(
346
+ `Property initializer is not object literal: ${property.initializer.kind}`,
347
+ "ModelAnalyzer"
348
+ );
349
+ }
350
+ if (objLiteral) {
351
+ const config = this.parseRelationshipConfig(objLiteral);
352
+ Logger.verbose("Parsed config:", "ModelAnalyzer");
353
+ if (this.verbose) {
354
+ console.log(config);
355
+ }
356
+ if (config) {
357
+ relationships[relationshipName] = config;
358
+ }
359
+ }
360
+ }
361
+ }
362
+ return relationships;
363
+ }
364
+ findSchemaForClass(node, schemaDefinitions, attachedSchemas) {
365
+ if (!node.members) return null;
366
+ for (const member of node.members) {
367
+ if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name) && member.name.text === "schema" && member.initializer && ts.isIdentifier(member.initializer)) {
368
+ const schema = schemaDefinitions.get(member.initializer.text);
369
+ if (schema) return schema;
370
+ }
371
+ }
372
+ for (const member of node.members) {
373
+ if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) && member.name.text === "getSchema" && member.body) {
374
+ for (const statement of member.body.statements) {
375
+ if (ts.isReturnStatement(statement) && statement.expression && ts.isCallExpression(statement.expression) && ts.isPropertyAccessExpression(statement.expression.expression) && ts.isIdentifier(statement.expression.expression.expression)) {
376
+ const schemaName = statement.expression.expression.expression.text;
377
+ const schema = schemaDefinitions.get(schemaName);
378
+ if (schema) return schema;
379
+ }
380
+ }
381
+ }
382
+ }
383
+ const attachedSchemaName = node.name ? attachedSchemas.get(node.name.text) : void 0;
384
+ if (attachedSchemaName) {
385
+ const schema = schemaDefinitions.get(attachedSchemaName);
386
+ if (schema) {
387
+ return schema;
388
+ }
389
+ }
390
+ return null;
391
+ }
392
+ extractAttachedSchemas(sourceFile) {
393
+ const attachments = /* @__PURE__ */ new Map();
394
+ const visit = (node) => {
395
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && (node.expression.text === "attachAndRegisterModel" || node.expression.text === "attachSchemaToClass")) {
396
+ const [modelArg, schemaArg] = node.arguments;
397
+ if (modelArg && schemaArg && ts.isIdentifier(modelArg) && ts.isIdentifier(schemaArg)) {
398
+ attachments.set(modelArg.text, schemaArg.text);
399
+ }
400
+ }
401
+ ts.forEachChild(node, visit);
402
+ };
403
+ visit(sourceFile);
404
+ return attachments;
405
+ }
406
+ extractCreateModelClassMetadata(node, schemaDefinitions, filePath) {
407
+ for (const declaration of node.declarationList.declarations) {
408
+ if (declaration.name && ts.isIdentifier(declaration.name) && declaration.initializer && ts.isCallExpression(declaration.initializer) && ts.isIdentifier(declaration.initializer.expression) && declaration.initializer.expression.text === "createModelClass" && declaration.initializer.arguments.length > 0) {
409
+ const config = declaration.initializer.arguments[0];
410
+ if (ts.isObjectLiteralExpression(config)) {
411
+ for (const property of config.properties) {
412
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && property.name.text === "schema" && ts.isIdentifier(property.initializer)) {
413
+ const schema = schemaDefinitions.get(
414
+ property.initializer.text
415
+ );
416
+ if (schema) {
417
+ return {
418
+ className: declaration.name.text,
419
+ modelName: schema.modelName,
420
+ schemaIdentifier: schema.identifier,
421
+ filePath,
422
+ relationships: schema.relationships,
423
+ fields: {}
424
+ };
425
+ }
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ return null;
432
+ }
433
+ /**
434
+ * Parse individual relationship configuration
435
+ */
436
+ parseRelationshipConfig(obj) {
437
+ const config = {};
438
+ for (const property of obj.properties) {
439
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
440
+ const propName = property.name.text;
441
+ if (ts.isStringLiteral(property.initializer)) {
442
+ config[propName] = property.initializer.text;
443
+ }
444
+ }
445
+ }
446
+ if (config.type && config.model) {
447
+ return config;
448
+ }
449
+ return null;
450
+ }
451
+ /**
452
+ * Extract field information from class
453
+ */
454
+ extractFields(node) {
455
+ const fields = {};
456
+ for (const member of node.members) {
457
+ if (ts.isPropertyDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
458
+ const fieldName = member.name.text;
459
+ const field = {
460
+ name: fieldName,
461
+ type: this.getTypeString(member.type),
462
+ isOptional: !!member.questionToken
463
+ };
464
+ const memberDecorators = ts.getDecorators?.(member) || member.decorators;
465
+ if (memberDecorators) {
466
+ for (const decorator of memberDecorators) {
467
+ if (ts.isCallExpression(decorator.expression) && ts.isIdentifier(decorator.expression.expression) && decorator.expression.expression.text === "Field") {
468
+ field.decoratorOptions = {};
469
+ }
470
+ }
471
+ }
472
+ fields[fieldName] = field;
473
+ }
474
+ }
475
+ return fields;
476
+ }
477
+ /**
478
+ * Get string representation of a type
479
+ */
480
+ getTypeString(typeNode) {
481
+ if (!typeNode) return "any";
482
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
483
+ return typeNode.typeName.text;
484
+ }
485
+ switch (typeNode.kind) {
486
+ case ts.SyntaxKind.StringKeyword:
487
+ return "string";
488
+ case ts.SyntaxKind.NumberKeyword:
489
+ return "number";
490
+ case ts.SyntaxKind.BooleanKeyword:
491
+ return "boolean";
492
+ default:
493
+ return "any";
494
+ }
495
+ }
496
+ };
497
+
498
+ // src/cli/generators/RelationshipTypeGenerator.ts
499
+ var path2 = __toESM(require("path"), 1);
500
+ var AUTO_HEADER_START = createMarker(
501
+ "BEGIN",
502
+ "HEADER",
503
+ "AUTO-GENERATED HEADER"
504
+ );
505
+ var AUTO_HEADER_END = createMarker("END", "HEADER", "AUTO-GENERATED HEADER");
506
+ var AUTO_FOOTER_START = createMarker(
507
+ "BEGIN",
508
+ "FOOTER",
509
+ "AUTO-GENERATED FOOTER"
510
+ );
511
+ var AUTO_FOOTER_END = createMarker("END", "FOOTER", "AUTO-GENERATED FOOTER");
512
+ var LEGACY_HEADER_START = "// \u{1F525}\u{1F525} BEGIN AUTO-GENERATED HEADER \u{1F525}\u{1F525}";
513
+ var LEGACY_HEADER_END = "// \u{1F525}\u{1F525} END AUTO-GENERATED HEADER \u{1F525}\u{1F525}";
514
+ var LEGACY_FOOTER_START = "// \u{1F525}\u{1F525} BEGIN AUTO-GENERATED FOOTER \u{1F525}\u{1F525}";
515
+ var LEGACY_FOOTER_END = "// \u{1F525}\u{1F525} END AUTO-GENERATED FOOTER \u{1F525}\u{1F525}";
516
+ function createMarker(pos, section, label) {
517
+ return `// \u{1F525}\u{1F525} ${pos} ${label} (DO NOT EDIT) \u{1F525}\u{1F525}`;
518
+ }
519
+ var RelationshipTypeGenerator = class {
520
+ models;
521
+ options;
522
+ modelsByName;
523
+ constructor(models, options) {
524
+ this.models = models;
525
+ this.options = options;
526
+ this.modelsByName = new Map(models.map((m) => [m.modelName, m]));
527
+ }
528
+ /**
529
+ * Check if we're running in a consumer project (has js-bao as dependency)
530
+ */
531
+ isConsumerProject() {
532
+ try {
533
+ const packageJsonPath = path2.resolve(process.cwd(), "package.json");
534
+ if (!require("fs").existsSync(packageJsonPath)) {
535
+ return false;
536
+ }
537
+ const packageJson = JSON.parse(
538
+ require("fs").readFileSync(packageJsonPath, "utf8")
539
+ );
540
+ return !!(packageJson.dependencies?.["js-bao"] || packageJson.devDependencies?.["js-bao"]);
541
+ } catch {
542
+ return false;
543
+ }
544
+ }
545
+ /**
546
+ * Generate TypeScript declaration files for all relationships
547
+ */
548
+ async generateTypeDeclarations() {
549
+ Logger.verbose(
550
+ "Generating type declarations...",
551
+ "RelationshipTypeGenerator"
552
+ );
553
+ if (!this.options.generatePerModelFiles) {
554
+ await this.generateSingleFile();
555
+ }
556
+ await this.generatePerModelFiles();
557
+ await this.updateModelTemplates();
558
+ Logger.verbose(
559
+ "Type declarations generated successfully.",
560
+ "RelationshipTypeGenerator"
561
+ );
562
+ }
563
+ /**
564
+ * Generate a single file with all relationship declarations
565
+ */
566
+ async generateSingleFile() {
567
+ const imports = this.generateImports();
568
+ const declarations = this.generateAllDeclarations();
569
+ const content = this.buildFileContent(imports, declarations);
570
+ const outputPath = path2.join(this.options.outputDir, "relationships.d.ts");
571
+ await FileUtils.writeFileContent(outputPath, content);
572
+ Logger.verbose(`Generated ${outputPath}`, "RelationshipTypeGenerator");
573
+ const tsContent = this.buildModuleFileContent(imports, declarations);
574
+ const tsOutputPath = path2.join(this.options.outputDir, "relationships.ts");
575
+ await FileUtils.writeFileContent(tsOutputPath, tsContent);
576
+ Logger.verbose(`Generated ${tsOutputPath}`, "RelationshipTypeGenerator");
577
+ const indexContent = this.buildIndexFileContent();
578
+ const indexPath = path2.join(this.options.outputDir, "index.ts");
579
+ await FileUtils.writeFileContent(indexPath, indexContent);
580
+ Logger.verbose(`Generated ${indexPath}`, "RelationshipTypeGenerator");
581
+ }
582
+ /**
583
+ * Generate separate files for each model
584
+ */
585
+ async generatePerModelFiles() {
586
+ for (const model of this.models) {
587
+ const imports = this.generateImportsForModel(model);
588
+ const declaration = Object.keys(model.relationships).length > 0 ? this.generateDeclarationForModel(model) : this.generateEmptyDeclaration(model);
589
+ const content = this.buildFileContent(imports, [declaration]);
590
+ const outputPath = path2.join(
591
+ this.options.outputDir,
592
+ `${model.className}.relationships.d.ts`
593
+ );
594
+ await FileUtils.writeFileContent(outputPath, content);
595
+ Logger.verbose(`Generated ${outputPath}`, "RelationshipTypeGenerator");
596
+ }
597
+ }
598
+ /**
599
+ * Generate import statements for all models and utility types
600
+ */
601
+ generateImports() {
602
+ const imports = [];
603
+ const hasRelationships = this.models.some(
604
+ (model) => Object.keys(model.relationships).length > 0
605
+ );
606
+ if (!hasRelationships) {
607
+ return imports;
608
+ }
609
+ const isConsumerProject = this.isConsumerProject();
610
+ const utilityTypesImport = isConsumerProject ? "import type { PaginationOptions, PaginatedResult } from 'js-bao';" : "import type { PaginationOptions, PaginatedResult } from '../types/relationshipMethods';";
611
+ imports.push(utilityTypesImport);
612
+ const usedModels = /* @__PURE__ */ new Set();
613
+ for (const model of this.models) {
614
+ if (Object.keys(model.relationships).length > 0) {
615
+ usedModels.add(model.className);
616
+ for (const config of Object.values(model.relationships)) {
617
+ const relatedModel = this.modelsByName.get(config.model);
618
+ if (relatedModel) {
619
+ usedModels.add(relatedModel.className);
620
+ }
621
+ }
622
+ }
623
+ }
624
+ for (const className of usedModels) {
625
+ const model = this.models.find((m) => m.className === className);
626
+ if (model) {
627
+ const relativePath = FileUtils.getRelativePath(
628
+ path2.join(this.options.outputDir, "relationships.d.ts"),
629
+ model.filePath
630
+ );
631
+ imports.push(`import { ${model.className} } from '${relativePath}';`);
632
+ }
633
+ }
634
+ return imports;
635
+ }
636
+ /**
637
+ * Generate imports for a specific model
638
+ */
639
+ generateImportsForModel(model) {
640
+ const imports = [];
641
+ const outputPath = path2.join(
642
+ this.options.outputDir,
643
+ `${model.className}.relationships.d.ts`
644
+ );
645
+ const isConsumerProject = this.isConsumerProject();
646
+ const utilityTypesImport = isConsumerProject ? "import type { PaginationOptions, PaginatedResult } from 'js-bao';" : `import type { PaginationOptions, PaginatedResult } from '${FileUtils.getRelativePath(
647
+ outputPath,
648
+ path2.join("src", "types", "relationshipMethods.ts")
649
+ )}';`;
650
+ imports.push(utilityTypesImport);
651
+ const relatedModelNames = /* @__PURE__ */ new Set();
652
+ for (const config of Object.values(model.relationships)) {
653
+ relatedModelNames.add(config.model);
654
+ }
655
+ for (const relatedModelName of relatedModelNames) {
656
+ const relatedModel = this.modelsByName.get(relatedModelName);
657
+ if (relatedModel && relatedModel !== model) {
658
+ const relativePath = FileUtils.getRelativePath(
659
+ outputPath,
660
+ relatedModel.filePath
661
+ );
662
+ imports.push(
663
+ `import { ${relatedModel.className} } from '${relativePath}';`
664
+ );
665
+ }
666
+ }
667
+ return imports;
668
+ }
669
+ /**
670
+ * Generate all model declarations
671
+ */
672
+ generateAllDeclarations() {
673
+ return this.models.filter((model) => Object.keys(model.relationships).length > 0).map((model) => this.generateDeclarationForModel(model));
674
+ }
675
+ /**
676
+ * Generate declaration for a specific model
677
+ */
678
+ generateDeclarationForModel(model) {
679
+ const relativePath = FileUtils.getRelativePath(
680
+ path2.join(this.options.outputDir, "relationships.d.ts"),
681
+ model.filePath
682
+ );
683
+ const methods = [];
684
+ for (const [relationshipName, config] of Object.entries(
685
+ model.relationships
686
+ )) {
687
+ const relatedModel = this.modelsByName.get(config.model);
688
+ if (!relatedModel) {
689
+ console.warn(
690
+ `[RelationshipTypeGenerator] Related model '${config.model}' not found for relationship '${relationshipName}' in model '${model.className}'`
691
+ );
692
+ continue;
693
+ }
694
+ switch (config.type) {
695
+ case "refersTo":
696
+ methods.push(
697
+ this.generateRefersToMethod(relationshipName, relatedModel)
698
+ );
699
+ break;
700
+ case "hasMany":
701
+ methods.push(
702
+ this.generateHasManyMethod(relationshipName, relatedModel)
703
+ );
704
+ break;
705
+ case "hasManyThrough":
706
+ methods.push(
707
+ ...this.generateHasManyThroughMethods(
708
+ relationshipName,
709
+ config,
710
+ relatedModel
711
+ )
712
+ );
713
+ break;
714
+ default:
715
+ console.warn(
716
+ `[RelationshipTypeGenerator] Unknown relationship type '${config.type}' for '${relationshipName}' in model '${model.className}'`
717
+ );
718
+ }
719
+ }
720
+ if (methods.length === 0) return "";
721
+ return `declare module '${relativePath}' {
722
+ interface ${model.className} {
723
+ ${methods.map((method) => ` ${method}`).join("\n")}
724
+ }
725
+ }`;
726
+ }
727
+ generateEmptyDeclaration(model) {
728
+ return [`// ${model.className} has no relationships.`, "export {};"].join(
729
+ "\n"
730
+ );
731
+ }
732
+ /**
733
+ * Generate method signature for refersTo relationship
734
+ */
735
+ generateRefersToMethod(relationshipName, relatedModel) {
736
+ return `${relationshipName}(): Promise<${relatedModel.className} | null>;`;
737
+ }
738
+ /**
739
+ * Generate method signature for hasMany relationship
740
+ */
741
+ generateHasManyMethod(relationshipName, relatedModel) {
742
+ return `${relationshipName}(options?: PaginationOptions): Promise<PaginatedResult<${relatedModel.className}>>;`;
743
+ }
744
+ /**
745
+ * Generate method signatures for hasManyThrough relationship
746
+ */
747
+ generateHasManyThroughMethods(relationshipName, config, relatedModel) {
748
+ const methods = [];
749
+ methods.push(
750
+ `${relationshipName}(options?: PaginationOptions): Promise<PaginatedResult<${relatedModel.className}>>;`
751
+ );
752
+ const singularName = this.getSingularName(config.model);
753
+ const capitalizedSingular = singularName.charAt(0).toUpperCase() + singularName.slice(1);
754
+ methods.push(
755
+ `add${capitalizedSingular}(target: ${relatedModel.className} | string): Promise<void>;`
756
+ );
757
+ methods.push(
758
+ `remove${capitalizedSingular}(target: ${relatedModel.className} | string): Promise<void>;`
759
+ );
760
+ return methods;
761
+ }
762
+ /**
763
+ * Get singular form of a model name (simple implementation)
764
+ */
765
+ getSingularName(modelName) {
766
+ if (modelName.endsWith("s")) {
767
+ return modelName.slice(0, -1);
768
+ }
769
+ return modelName;
770
+ }
771
+ /**
772
+ * Build the final file content
773
+ */
774
+ buildFileContent(imports, declarations) {
775
+ const header = [
776
+ "// AUTO-GENERATED FILE - DO NOT EDIT",
777
+ "// This file was generated by js-bao-codegen",
778
+ "// Run `npx js-bao-codegen` to regenerate",
779
+ ""
780
+ ];
781
+ const content = [
782
+ ...header,
783
+ ...imports,
784
+ "",
785
+ ...declarations.filter((d) => d.length > 0),
786
+ ""
787
+ ];
788
+ return content.join("\n");
789
+ }
790
+ /**
791
+ * Build module file content (.ts file for better resolution)
792
+ */
793
+ buildModuleFileContent(imports, declarations) {
794
+ const header = [
795
+ "// AUTO-GENERATED FILE - DO NOT EDIT",
796
+ "// This file was generated by js-bao-codegen",
797
+ "// Run `npx js-bao-codegen` to regenerate",
798
+ ""
799
+ ];
800
+ const content = [
801
+ ...header,
802
+ ...imports,
803
+ "",
804
+ ...declarations.filter((d) => d.length > 0),
805
+ "",
806
+ "// Empty export to make this a module",
807
+ "export {};"
808
+ ];
809
+ return content.join("\n");
810
+ }
811
+ /**
812
+ * Build index file content for easier importing
813
+ */
814
+ buildIndexFileContent() {
815
+ return [
816
+ "// AUTO-GENERATED FILE - DO NOT EDIT",
817
+ "// This file was generated by js-bao-codegen",
818
+ "// Run `npx js-bao-codegen` to regenerate",
819
+ "",
820
+ "// Import relationship types to enable declaration merging",
821
+ "import './relationships';",
822
+ "",
823
+ "// Empty export to make this a module",
824
+ "export {};"
825
+ ].join("\n");
826
+ }
827
+ /**
828
+ * Update every model file with the new auto-generated header/footer blocks
829
+ */
830
+ async updateModelTemplates() {
831
+ for (const model of this.models) {
832
+ try {
833
+ await this.applyAutoBlocksToModel(model);
834
+ } catch (error) {
835
+ Logger.warn(
836
+ `Failed to update model file ${model.filePath}: ${error}`,
837
+ "RelationshipTypeGenerator"
838
+ );
839
+ }
840
+ }
841
+ }
842
+ async applyAutoBlocksToModel(model) {
843
+ if (!model.schemaIdentifier) {
844
+ Logger.warn(
845
+ `Skipping ${model.className}: could not determine schema identifier.`,
846
+ "RelationshipTypeGenerator"
847
+ );
848
+ return;
849
+ }
850
+ if (!await FileUtils.fileExists(model.filePath)) {
851
+ Logger.warn(
852
+ `Model file ${model.filePath} does not exist. Skipping.`,
853
+ "RelationshipTypeGenerator"
854
+ );
855
+ return;
856
+ }
857
+ let content = await FileUtils.readFileContent(model.filePath);
858
+ const originalContent = content;
859
+ content = this.stripLegacyRelationshipImports(content, model.className);
860
+ content = this.removeBlock(content, AUTO_HEADER_START, AUTO_HEADER_END);
861
+ content = this.removeBlock(content, LEGACY_HEADER_START, LEGACY_HEADER_END);
862
+ const headerBlock = this.buildAutoHeaderBlock(model);
863
+ const headerInsertIndex = this.findSchemaDeclarationEnd(
864
+ content,
865
+ model.schemaIdentifier
866
+ );
867
+ content = this.insertBlock(
868
+ content,
869
+ headerInsertIndex,
870
+ headerBlock
871
+ );
872
+ content = this.ensureClassDeclaration(content, model.className);
873
+ content = this.removeBlock(content, AUTO_FOOTER_START, AUTO_FOOTER_END);
874
+ content = this.removeBlock(content, LEGACY_FOOTER_START, LEGACY_FOOTER_END);
875
+ const footerBlock = this.buildAutoFooterBlock(model);
876
+ content = this.insertBlock(content, content.length, footerBlock);
877
+ content = this.normalizeWhitespace(content);
878
+ if (content !== originalContent) {
879
+ if (!content.endsWith("\n")) {
880
+ content += "\n";
881
+ }
882
+ await FileUtils.writeFileContent(model.filePath, content);
883
+ Logger.verbose(
884
+ `Updated auto-generation blocks in ${model.filePath}`,
885
+ "RelationshipTypeGenerator"
886
+ );
887
+ }
888
+ }
889
+ buildAutoHeaderBlock(model) {
890
+ const lines = [
891
+ "",
892
+ AUTO_HEADER_START,
893
+ "/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */",
894
+ 'import type { InferAttrs } from "js-bao";',
895
+ 'import { attachAndRegisterModel } from "js-bao";',
896
+ "",
897
+ `export type ${model.className}Attrs = InferAttrs<typeof ${model.schemaIdentifier}>;`,
898
+ `export interface ${model.className} extends ${model.className}Attrs, BaseModel {}`,
899
+ AUTO_HEADER_END,
900
+ ""
901
+ ];
902
+ return lines.join("\n");
903
+ }
904
+ buildAutoFooterBlock(model) {
905
+ const lines = ["", AUTO_FOOTER_START];
906
+ if (Object.keys(model.relationships).length > 0) {
907
+ lines.push(
908
+ `import "./generated/${model.className}.relationships.d";`,
909
+ ""
910
+ );
911
+ }
912
+ lines.push(
913
+ `attachAndRegisterModel(${model.className}, ${model.schemaIdentifier});`,
914
+ AUTO_FOOTER_END,
915
+ ""
916
+ );
917
+ return lines.join("\n");
918
+ }
919
+ removeBlock(content, startMarker, endMarker) {
920
+ const markerRegex = new RegExp(
921
+ `${this.escapeRegExp(startMarker)}[\\s\\S]*?${this.escapeRegExp(
922
+ endMarker
923
+ )}`,
924
+ "m"
925
+ );
926
+ let updated = content;
927
+ while (markerRegex.test(updated)) {
928
+ updated = updated.replace(markerRegex, "");
929
+ }
930
+ return updated;
931
+ }
932
+ insertBlock(content, insertionIndex, blockContent) {
933
+ const index = insertionIndex >= 0 && insertionIndex <= content.length ? insertionIndex : content.length;
934
+ if (index === 0) {
935
+ return `${blockContent}${content}`;
936
+ }
937
+ return `${content.slice(0, index)}${blockContent}${content.slice(index)}`;
938
+ }
939
+ findSchemaDeclarationEnd(content, schemaIdentifier) {
940
+ const pattern = new RegExp(
941
+ `const\\s+${schemaIdentifier}\\s*=\\s*defineModelSchema\\s*\\(`,
942
+ "m"
943
+ );
944
+ const match = pattern.exec(content);
945
+ if (!match) {
946
+ return -1;
947
+ }
948
+ let depth = 0;
949
+ const start = content.indexOf("(", match.index);
950
+ if (start === -1) {
951
+ return -1;
952
+ }
953
+ for (let i = start; i < content.length; i++) {
954
+ const char = content[i];
955
+ if (char === "(") {
956
+ depth++;
957
+ } else if (char === ")") {
958
+ depth--;
959
+ if (depth === 0) {
960
+ const semicolonIndex = content.indexOf(";", i);
961
+ return semicolonIndex === -1 ? i + 1 : semicolonIndex + 1;
962
+ }
963
+ }
964
+ }
965
+ return -1;
966
+ }
967
+ escapeRegExp(value) {
968
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
969
+ }
970
+ stripLegacyRelationshipImports(content, className) {
971
+ const patterns = [
972
+ /\s*\/\/\s*Auto-generated relationship types\s*/g,
973
+ new RegExp(
974
+ `\\s*import\\s+['"]\\.\\/?[^'"]*${className}\\.relationships\\.d(\\.ts)?['"]\\s*;?`,
975
+ "g"
976
+ ),
977
+ /\s*import\s+['"]\.[^'"]*\.relationships\.d(\.ts)?['"]\s*;?/g
978
+ ];
979
+ let updated = content;
980
+ for (const pattern of patterns) {
981
+ updated = updated.replace(pattern, "\n");
982
+ }
983
+ return updated;
984
+ }
985
+ ensureClassDeclaration(content, className) {
986
+ const classRegex = new RegExp(`export\\s+class\\s+${className}\\b`);
987
+ if (classRegex.test(content)) {
988
+ return content;
989
+ }
990
+ const stub = [
991
+ "",
992
+ `export class ${className} extends BaseModel {`,
993
+ "}",
994
+ ""
995
+ ].join("\n");
996
+ const footerIndex = content.indexOf(AUTO_FOOTER_START);
997
+ const insertionIndex = footerIndex === -1 ? content.length : footerIndex;
998
+ return `${content.slice(0, insertionIndex)}${stub}${content.slice(
999
+ insertionIndex
1000
+ )}`;
1001
+ }
1002
+ normalizeWhitespace(content) {
1003
+ return content.replace(/\n{3,}/g, "\n\n");
1004
+ }
1005
+ };
1006
+
1007
+ // package.json
1008
+ var package_default = {
1009
+ name: "js-bao",
1010
+ version: "0.2.8",
1011
+ description: "A library providing data modeling capabilities which support live updates and queries.",
1012
+ types: "dist/index.d.ts",
1013
+ type: "module",
1014
+ files: [
1015
+ "dist"
1016
+ ],
1017
+ exports: {
1018
+ ".": {
1019
+ types: "./dist/index.d.ts",
1020
+ node: {
1021
+ import: "./dist/node.js",
1022
+ require: "./dist/node.cjs"
1023
+ },
1024
+ browser: {
1025
+ import: "./dist/browser.js",
1026
+ require: "./dist/browser.cjs"
1027
+ },
1028
+ default: {
1029
+ import: "./dist/index.js",
1030
+ require: "./dist/index.cjs"
1031
+ }
1032
+ },
1033
+ "./node": {
1034
+ types: "./dist/node.d.ts",
1035
+ import: "./dist/node.js",
1036
+ require: "./dist/node.cjs"
1037
+ },
1038
+ "./browser": {
1039
+ types: "./dist/browser.d.ts",
1040
+ import: "./dist/browser.js",
1041
+ require: "./dist/browser.cjs"
1042
+ }
1043
+ },
1044
+ bin: {
1045
+ "js-bao-codegen": "./dist/codegen.cjs",
1046
+ "jsbao-codegen": "./dist/codegen.cjs"
1047
+ },
1048
+ scripts: {
1049
+ build: "npm run build:cli && npm run codegen && rm -f dist/index.* dist/node.* dist/browser.* && npm run build:main",
1050
+ "build:main": "npm run build:browser && npm run build:node && npm run build:universal",
1051
+ "build:browser": "tsup src/browser.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --no-splitting",
1052
+ "build:node": "tsup src/node.ts --format esm,cjs --dts --platform node --external better-sqlite3 --external sql.js --external async-mutex --external ulid --no-splitting",
1053
+ "build:universal": "tsup src/index.ts --format esm,cjs --dts --external better-sqlite3 --external sql.js --external async-mutex --external ulid --no-splitting",
1054
+ "build:cli": "tsup src/cli/codegen.ts --format cjs --dts --platform node --target node18 --external commander --external typescript --external fs --external path --external util --no-splitting",
1055
+ dev: 'npm run build:cli && concurrently "npm run codegen:watch" "npm run dev:main"',
1056
+ "dev:main": 'concurrently "npm run build:browser:dev --watch" "npm run build:node:dev --watch" "npm run build:universal:dev --watch"',
1057
+ "dev:simple": "npm run build:cli && npm run codegen && npm run build:main --watch",
1058
+ "build:browser:dev": "tsup src/browser.ts --format esm,cjs --dts --sourcemap --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid",
1059
+ "build:node:dev": "tsup src/node.ts --format esm,cjs --dts --sourcemap --platform node --external better-sqlite3 --external sql.js --external async-mutex --external ulid",
1060
+ "build:universal:dev": "tsup src/index.ts --format esm,cjs --dts --sourcemap --external better-sqlite3 --external sql.js --external async-mutex --external ulid",
1061
+ codegen: "./dist/codegen.cjs --config js-bao.config.cjs || echo 'Codegen skipped (likely in consumer project)'",
1062
+ "codegen:watch": "./dist/codegen.cjs --config js-bao.config.cjs --watch",
1063
+ "codegen:consumer": `node -e "require('child_process').spawn('./node_modules/.bin/js-bao-codegen', process.argv.slice(2), {stdio: 'inherit'})"`,
1064
+ test: 'echo "Error: no test specified" && exit 1',
1065
+ prepare: "npm run build",
1066
+ prepublishOnly: "npm run build"
1067
+ },
1068
+ dependencies: {
1069
+ "async-mutex": "^0.5.0",
1070
+ "sql.js": "^1.13.0",
1071
+ ulid: "^3.0.0"
1072
+ },
1073
+ devDependencies: {
1074
+ "@types/better-sqlite3": "^7.6.9",
1075
+ "@types/node": "^20.17.51",
1076
+ "@types/sql.js": "^1.4.9",
1077
+ commander: "^11.0.0",
1078
+ concurrently: "^9.1.2",
1079
+ tsup: "^8.0.2",
1080
+ tsx: "^4.19.4",
1081
+ typescript: "^5.4.5",
1082
+ yjs: "^13.6.18"
1083
+ },
1084
+ peerDependencies: {
1085
+ yjs: "^13.6.18"
1086
+ },
1087
+ optionalDependencies: {
1088
+ "better-sqlite3": "^9.6.0"
1089
+ },
1090
+ keywords: [
1091
+ "yjs",
1092
+ "crdt",
1093
+ "orm",
1094
+ "sqljs",
1095
+ "database",
1096
+ "reactivedb",
1097
+ "nodejs",
1098
+ "browser",
1099
+ "sqlite"
1100
+ ],
1101
+ author: "Primitive LLC",
1102
+ license: "UNLICENSED"
1103
+ };
1104
+
1105
+ // src/cli/codegen.ts
1106
+ var CodegenCLI = class {
1107
+ program;
1108
+ constructor() {
1109
+ this.program = new import_commander.Command();
1110
+ this.setupCommands();
1111
+ }
1112
+ setupCommands() {
1113
+ this.program.name("js-bao-codegen").description(
1114
+ "Auto-generate TypeScript declarations for js-bao relationship methods"
1115
+ ).version("1.0.0");
1116
+ this.program.option(
1117
+ "-i, --input <dir>",
1118
+ "Input directory containing model files",
1119
+ "./src/models"
1120
+ ).option(
1121
+ "-o, --output <dir>",
1122
+ "Output directory for generated files",
1123
+ "./src/models/generated"
1124
+ ).option("-c, --config <file>", "Path to configuration file").option("-w, --watch", "Watch for changes and regenerate", false).option("--exclude <patterns...>", "Exclude patterns (glob)", [
1125
+ "**/*.test.ts",
1126
+ "**/*.spec.ts"
1127
+ ]).option(
1128
+ "--generate-per-model-files",
1129
+ "Generate separate files for each model",
1130
+ false
1131
+ ).option(
1132
+ "--no-include-pagination",
1133
+ "Exclude pagination types from output",
1134
+ false
1135
+ ).option("-v, --verbose", "Show detailed logging information", false).action(async (options) => {
1136
+ await this.run(options);
1137
+ });
1138
+ }
1139
+ async run(cliOptions) {
1140
+ try {
1141
+ Logger.log(
1142
+ `js-bao-codegen v${package_default.version ?? "unknown"}`,
1143
+ "js-bao-codegen"
1144
+ );
1145
+ Logger.setVerbose(cliOptions.verbose);
1146
+ Logger.verbose(
1147
+ "Starting TypeScript relationship declaration generation...",
1148
+ "js-bao-codegen"
1149
+ );
1150
+ const config = await this.loadConfig(cliOptions);
1151
+ Logger.verbose("Configuration:", "js-bao-codegen");
1152
+ if (cliOptions.verbose) {
1153
+ console.log(config);
1154
+ }
1155
+ const excludePatternsWithOutput = [
1156
+ ...config.exclude,
1157
+ `**/${path3.relative(config.input, config.output)}/**`,
1158
+ `${path3.relative(config.input, config.output)}/**`
1159
+ ];
1160
+ const resolvedInputDir = path3.resolve(config.input);
1161
+ Logger.log(
1162
+ `Scanning for model files in ${resolvedInputDir}`,
1163
+ "js-bao-codegen"
1164
+ );
1165
+ Logger.verbose(
1166
+ `Exclude patterns: ${excludePatternsWithOutput.join(", ")}`,
1167
+ "js-bao-codegen"
1168
+ );
1169
+ const modelFiles = await this.findModelFiles(
1170
+ config.input,
1171
+ excludePatternsWithOutput
1172
+ );
1173
+ const formattedList = modelFiles.length > 0 ? modelFiles.map((file) => ` - ${file}`).join("\n") : " (none)";
1174
+ Logger.log(
1175
+ `Found ${modelFiles.length} model file(s):
1176
+ ${formattedList}`,
1177
+ "js-bao-codegen"
1178
+ );
1179
+ if (modelFiles.length === 0) {
1180
+ Logger.warn("No model files found. Exiting.", "js-bao-codegen");
1181
+ return;
1182
+ }
1183
+ const analyzer = new ModelAnalyzer(modelFiles, cliOptions.verbose);
1184
+ const models = await analyzer.analyzeModels();
1185
+ Logger.verbose(
1186
+ `Analyzed ${models.length} models with relationships`,
1187
+ "js-bao-codegen"
1188
+ );
1189
+ if (models.length === 0) {
1190
+ Logger.warn(
1191
+ "No models with relationships found. Exiting.",
1192
+ "js-bao-codegen"
1193
+ );
1194
+ return;
1195
+ }
1196
+ const generationOptions = {
1197
+ outputDir: config.output,
1198
+ modelsDir: config.input,
1199
+ generatePerModelFiles: config.generatePerModelFiles,
1200
+ includePagination: config.includePagination
1201
+ };
1202
+ const generator = new RelationshipTypeGenerator(
1203
+ models,
1204
+ generationOptions
1205
+ );
1206
+ await generator.generateTypeDeclarations();
1207
+ Logger.log("Generation completed successfully!", "js-bao-codegen");
1208
+ if (config.watch) {
1209
+ await this.setupWatchMode(config, modelFiles);
1210
+ }
1211
+ } catch (error) {
1212
+ Logger.error("Error: " + error, "js-bao-codegen");
1213
+ process.exit(1);
1214
+ }
1215
+ }
1216
+ async loadConfig(cliOptions) {
1217
+ let config = { ...cliOptions };
1218
+ if (cliOptions.config) {
1219
+ try {
1220
+ const configPath = path3.resolve(cliOptions.config);
1221
+ const configExists = await FileUtils.fileExists(configPath);
1222
+ if (configExists) {
1223
+ const configContent = await FileUtils.readFileContent(configPath);
1224
+ let configFile;
1225
+ if (configPath.endsWith(".json")) {
1226
+ configFile = JSON.parse(configContent);
1227
+ } else {
1228
+ delete require.cache[require.resolve(configPath)];
1229
+ configFile = require(configPath);
1230
+ }
1231
+ config = {
1232
+ input: cliOptions.input !== "./src/models" ? cliOptions.input : configFile.modelsDir || "./src/models",
1233
+ output: cliOptions.output !== "./src/models/generated" ? cliOptions.output : configFile.outputDir || "./src/models/generated",
1234
+ config: cliOptions.config,
1235
+ watch: cliOptions.watch,
1236
+ exclude: cliOptions.exclude.length > 2 ? cliOptions.exclude : configFile.exclude || ["**/*.test.ts", "**/*.spec.ts"],
1237
+ generatePerModelFiles: cliOptions.generatePerModelFiles || configFile.generatePerModelFiles || false,
1238
+ includePagination: configFile.includePagination ?? true,
1239
+ verbose: cliOptions.verbose
1240
+ };
1241
+ } else {
1242
+ Logger.warn(`Config file not found: ${configPath}`, "js-bao-codegen");
1243
+ }
1244
+ } catch (error) {
1245
+ Logger.error(`Error loading config file: ${error}`, "js-bao-codegen");
1246
+ throw error;
1247
+ }
1248
+ }
1249
+ return config;
1250
+ }
1251
+ async findModelFiles(inputDir, exclude) {
1252
+ const resolvedInputDir = path3.resolve(inputDir);
1253
+ return await FileUtils.findTSFiles(resolvedInputDir, exclude);
1254
+ }
1255
+ async setupWatchMode(config, initialFiles) {
1256
+ Logger.log("Watch mode enabled. Watching for changes...", "js-bao-codegen");
1257
+ const checkInterval = 2e3;
1258
+ let lastModified = /* @__PURE__ */ new Map();
1259
+ for (const file of initialFiles) {
1260
+ try {
1261
+ const stats = await import("fs/promises").then((fs2) => fs2.stat(file));
1262
+ lastModified.set(file, stats.mtimeMs);
1263
+ } catch (error) {
1264
+ Logger.warn(`Could not stat file ${file}: ${error}`, "js-bao-codegen");
1265
+ }
1266
+ }
1267
+ const poll = async () => {
1268
+ try {
1269
+ let hasChanges = false;
1270
+ for (const file of initialFiles) {
1271
+ try {
1272
+ const stats = await import("fs/promises").then(
1273
+ (fs2) => fs2.stat(file)
1274
+ );
1275
+ const lastMod = lastModified.get(file) || 0;
1276
+ if (stats.mtimeMs > lastMod) {
1277
+ Logger.log(`Detected change in ${file}`, "js-bao-codegen");
1278
+ lastModified.set(file, stats.mtimeMs);
1279
+ hasChanges = true;
1280
+ }
1281
+ } catch (error) {
1282
+ if (lastModified.has(file)) {
1283
+ Logger.log(`File deleted: ${file}`, "js-bao-codegen");
1284
+ lastModified.delete(file);
1285
+ hasChanges = true;
1286
+ }
1287
+ }
1288
+ }
1289
+ const excludePatternsWithOutput = [
1290
+ ...config.exclude,
1291
+ `**/${path3.relative(config.input, config.output)}/**`,
1292
+ `${path3.relative(config.input, config.output)}/**`
1293
+ ];
1294
+ const currentFiles = await this.findModelFiles(
1295
+ config.input,
1296
+ excludePatternsWithOutput
1297
+ );
1298
+ const newFiles = currentFiles.filter((f) => !lastModified.has(f));
1299
+ if (newFiles.length > 0) {
1300
+ Logger.log(`Detected ${newFiles.length} new files`, "js-bao-codegen");
1301
+ for (const file of newFiles) {
1302
+ const stats = await import("fs/promises").then(
1303
+ (fs2) => fs2.stat(file)
1304
+ );
1305
+ lastModified.set(file, stats.mtimeMs);
1306
+ }
1307
+ hasChanges = true;
1308
+ }
1309
+ if (hasChanges) {
1310
+ Logger.log("Regenerating types...", "js-bao-codegen");
1311
+ await this.run({ ...config, watch: false });
1312
+ }
1313
+ } catch (error) {
1314
+ Logger.error("Error in watch mode: " + error, "js-bao-codegen");
1315
+ }
1316
+ setTimeout(poll, checkInterval);
1317
+ };
1318
+ setTimeout(poll, checkInterval);
1319
+ }
1320
+ execute() {
1321
+ this.program.parse(process.argv);
1322
+ }
1323
+ };
1324
+ var cli = new CodegenCLI();
1325
+ cli.execute();